import { parseDescriptionDefault } from "./register_parsers/reg_default";
import { parseDescriptionF0 } from "./register_parsers/reg_f0";
import { parseDescription44 } from "./register_parsers/reg_44";

export interface RegisterBitwiseOperation {
  bits: string;
  description: string;
  value?: string;
  footnoteRef?: string;
}

export interface Note {
  ref: string;
  text: string;
}

export interface RegisterAccess {
  description?: string;
  operations: RegisterBitwiseOperation[];
  notes: Note[];
}

export interface RegisterDetail {
  modeName?: string;
  read?: RegisterAccess;
  write?: RegisterAccess;
  common?: RegisterAccess;
  text: string;
}

export interface Register {
  hex_address: string;
  dec_address: number | string;
  name: string;
  modes: RegisterDetail[];
  description: string;
  issue_4_only: boolean;
  source: string[];
  search: string;
  wiki_link: string;
  text: string;
  notes: Note[];
}

/**
 * Detects a source line marking the whole register as restricted to certain
 * board issues, e.g. "Issue 4 Only" or "Issues 4 and 5 Only". Matched loosely
 * across wording so upstream rewording (the tbblue source has used both forms)
 * keeps setting the flag rather than silently dropping the badge.
 *
 * Case-sensitive on purpose: register-level markers are capitalised, while an
 * incidental per-bit caveat like "(issue 5 only)" (e.g. nextreg 0x81 bit 3) is
 * lowercase and must not flag the entire register.
 */
export function isIssueRestricted(line: string): boolean {
  return /Issues?\b.*\bOnly/.test(line);
}

/**
 * True when a parsed RegisterDetail carries something worth rendering — a mode
 * name, an access block, or mode-level text. Body-less registers (e.g. the
 * "Reserved" entries 0xC7/0xCB/0xCF/0xFF) would otherwise contribute an empty
 * mode that renders as a stray, contentless tab strip. Parsers call this before
 * pushing a detail into reg.modes.
 */
export function detailHasContent(detail: RegisterDetail): boolean {
  return Boolean(
    detail.modeName ||
    detail.read ||
    detail.write ||
    detail.common ||
    (detail.text && detail.text.trim())
  );
}

/**
 * Parses the content of the nextreg.txt file and returns an array of register objects.
 * @param fileContent The content of the nextreg.txt file.
 * @returns A promise that resolves to an array of Register objects.
 */
export function parseNextReg(fileContent: string): Register[] {
  const registers: Register[] = [];
  const paragraphs = fileContent.split(/\n\s*\n/);

  for (const paragraph of paragraphs) {
    if (!paragraph.trim()) {
      continue;
    }
    processRegisterBlock(paragraph, registers);
  }

  return registers;
}

export function processRegisterBlock(paragraph: string, registers: Register[]) {
  const lines = paragraph.trim().split('\n');
  const firstLine = lines[0];

  // Skip commented-out register blocks. The header regex below is not anchored,
  // so a disabled entry like "// 0xA3 (163) => ..." would otherwise match and
  // leak a phantom register into the output.
  if (firstLine.trim().startsWith('//')) {
    return;
  }

  const registerMatch = firstLine.match(/([0-9a-fA-F,x]+)\s*\((.*?)\)\s*=>\s*(.*)/);

  if (!registerMatch) {
    return;
  }

  const hexAddresses = registerMatch[1].trim();
  const decAddresses = registerMatch[2].trim();
  const name = registerMatch[3] ? registerMatch[3].trim() : '';
  const description = lines.slice(1).join('\n').trim();

  const hexList = hexAddresses.split(',').map(h => h.trim());
  const decList = decAddresses.includes('-') ? decAddresses.split('-') : decAddresses.split(',').map(d => d.trim());

  const normalizeHex = (hex: string): string => {
    let h = hex.trim();
    if (!/^0x/i.test(h)) {
      h = '0x' + h;
    }
    return '0x' + h.slice(2).toUpperCase();
  };

  const makeWikiLink = (hex: string, name: string): string => {
    let link = `https://wiki.specnext.dev/`;

    switch (hex) {
      case '0x75':
      case '0x76':
      case '0x77':
      case '0x78':
      case '0x79':
        // contemplate hoisting this to outside the switch...
        link += `${name.replace(/with automatic post increment of Sprite Number/, "(with_INC)").replace(/ /g, "_")}_Register`;
        break;
      case '0x7F':
        // contemplate hoisting this to outside the switch...
        link += `${name.replace(/Register/, "Storage").replace(/ /g, "_")}_Register`;
        break;

      case '0xF8':
        link += `${name.replace(/ /g, "_")}`;
        break;
      default:
        link += `${name.replace(/ /g, "_")}_Register`;
    }

    return link;
  }

  const createRegister = (hex: string, dec: string | number, regName: string): Register => {
    const reg: Register = {
      hex_address: hex,
      dec_address: dec,
      name: regName,
      description: description,
      modes: [],
      issue_4_only: false,
      source: [],
      search: firstLine.toLowerCase(),
      wiki_link: "",
      text: "",
      notes: [],
    };

    // Dispatch to the appropriate parser based on hex
    const hexKey = normalizeHex(hex);
    switch (hexKey) {
      case '0xF0':
        parseDescriptionF0(reg, description);
        break;
      case '0x44':
        parseDescription44(reg, description);
        break;
      default:
        parseDescriptionDefault(reg, description);
        break;
    }

    reg.wiki_link = makeWikiLink(hexKey, reg.name);

    return reg;
  };

  if (hexList.length > 1) {
    for (let i = 0; i < hexList.length; i++) {
      const hexAddr = hexList[i];
      const decAddr = decList[i] || decAddresses;
      const dec = isNaN(parseInt(decAddr, 10)) ? decAddr : parseInt(decAddr, 10);
      registers.push(createRegister(hexAddr, dec, `${name} (${hexAddr})`));
    }
  } else {
    const dec = isNaN(parseInt(decAddresses, 10)) ? decAddresses : parseInt(decAddresses, 10);
    registers.push(createRegister(hexAddresses, dec, name));
  }
}
