import { notFound } from "next/navigation";
import { micromark } from "micromark";
import { LabContent } from "~/components/labs/LabContent";
import { getLabDocument, getLabSummaries } from "~/lib/labs";
type ObjectiveStyle = "divider" | "cards" | "rail";
const objectiveStyles = new Set<ObjectiveStyle>(["divider", "cards", "rail"]);
type StepStyle = "underline" | "pills" | "blocks";
const stepStyles = new Set<StepStyle>(["underline", "pills", "blocks"]);
type BreakoutStyle = "none" | "panel" | "workflow" | "command-pills" | "instruction-rails";
const breakoutStyles = new Set<BreakoutStyle>([
  "none",
  "panel",
  "workflow",
  "command-pills",
  "instruction-rails",
]);

function normalizeObjectiveStyle(style: unknown): ObjectiveStyle | null {
  if (typeof style !== "string") return null;
  const normalized = style.trim().toLowerCase() as ObjectiveStyle;
  return objectiveStyles.has(normalized) ? normalized : null;
}

function normalizeStepStyle(style: unknown): StepStyle | null {
  if (typeof style !== "string") return null;
  const normalized = style.trim().toLowerCase() as StepStyle;
  return stepStyles.has(normalized) ? normalized : null;
}

function normalizeBreakoutStyle(style: unknown): BreakoutStyle | null {
  if (typeof style !== "string") return null;
  const normalized = style.trim().toLowerCase() as BreakoutStyle;
  return breakoutStyles.has(normalized) ? normalized : null;
}

function extractObjectiveStyleDirective(markdown: string) {
  const styleDirectivePattern = /<!--\s*objective-style:\s*([a-z-]+)\s*-->/i;
  const match = styleDirectivePattern.exec(markdown);
  const style = normalizeObjectiveStyle(match?.[1]);

  if (!match) {
    return { markdown, style };
  }

  const cleanedMarkdown = markdown.replace(styleDirectivePattern, "").trimStart();
  return { markdown: cleanedMarkdown, style };
}

function extractBreakoutStyleDirective(markdown: string) {
  const styleDirectivePattern = /<!--\s*breakout-style:\s*([a-z-]+)\s*-->/i;
  const match = styleDirectivePattern.exec(markdown);
  const style = normalizeBreakoutStyle(match?.[1]);

  if (!match) {
    return { markdown, style };
  }

  const cleanedMarkdown = markdown.replace(styleDirectivePattern, "").trimStart();
  return { markdown: cleanedMarkdown, style };
}

function extractStepStyleDirective(markdown: string) {
  const styleDirectivePattern = /<!--\s*step-style:\s*([a-z-]+)\s*-->/i;
  const match = styleDirectivePattern.exec(markdown);
  const style = normalizeStepStyle(match?.[1]);

  if (!match) {
    return { markdown, style };
  }

  const cleanedMarkdown = markdown.replace(styleDirectivePattern, "").trimStart();
  return { markdown: cleanedMarkdown, style };
}

function extractPlainText(htmlText: string) {
  return htmlText
    .replace(/<[^>]+>/g, "")
    .replace(/&nbsp;/g, " ")
    .trim();
}

function isObjectiveHeading(headingHtml: string) {
  const plainText = extractPlainText(headingHtml);
  return /^objective\b/i.test(plainText);
}

function stripObjectiveDividers(html: string) {
  return html.replace(
    /<hr\s*\/?>\s*(?=<h2(?:\s+[^>]*)?>\s*Objective\b)/gi,
    "",
  );
}

function transformOutsideDetails(
  html: string,
  transform: (safeHtml: string) => string,
) {
  const detailsPattern = /<details(?:\s+[^>]*)?>[\s\S]*?<\/details>/gi;
  const detailsBlocks: string[] = [];

  const maskedHtml = html.replace(detailsPattern, (detailsBlock) => {
    const token = `__DETAILS_BLOCK_${detailsBlocks.length}__`;
    detailsBlocks.push(detailsBlock);
    return token;
  });

  const transformedHtml = transform(maskedHtml);
  return transformedHtml.replace(/__DETAILS_BLOCK_(\d+)__/g, (_, indexText: string) => {
    const index = Number(indexText);
    return detailsBlocks[index] ?? "";
  });
}

function segmentObjectiveSections(html: string) {
  const headingPattern = /<h2(?:\s+[^>]*)?>([\s\S]*?)<\/h2>/gi;
  const headings: Array<{ index: number; text: string }> = [];
  let match = headingPattern.exec(html);

  while (match) {
    headings.push({ index: match.index, text: match[1] ?? "" });
    match = headingPattern.exec(html);
  }

  if (headings.length === 0) return html;

  let output = "";
  let cursor = 0;

  for (let i = 0; i < headings.length; i++) {
    const heading = headings[i];
    if (!isObjectiveHeading(heading.text)) continue;

    const nextObjectiveHeading = headings.slice(i + 1).find((nextHeading) => {
      return isObjectiveHeading(nextHeading.text);
    });
    const nextHeadingIndex = nextObjectiveHeading?.index ?? html.length;
    output += html.slice(cursor, heading.index);
    output += '<section class="objective-segment">';
    output += html.slice(heading.index, nextHeadingIndex);
    output += "</section>";
    cursor = nextHeadingIndex;
  }

  output += html.slice(cursor);
  return output;
}

function isStepHeading(headingHtml: string) {
  const plainText = extractPlainText(headingHtml);
  if (!plainText) return false;

  return (
    /^step\b/i.test(plainText) ||
    /^\d+(?:\.\d+)*(?:[.):])?\s+/.test(plainText) ||
    /\b(explore|execute|checkpoint|review)\b/i.test(plainText)
  );
}

function getStepMode(headingHtml: string) {
  const plainText = extractPlainText(headingHtml).toLowerCase();
  if (plainText.includes("execute")) return "execute";
  if (plainText.includes("explore")) return "explore";
  if (plainText.includes("checkpoint")) return "checkpoint";
  if (plainText.includes("review")) return "review";
  return null;
}

function looksLikeCommandBlock(codeHtml: string) {
  const codeText = codeHtml
    .replace(/<[^>]+>/g, "")
    .replace(/&lt;/g, "<")
    .replace(/&gt;/g, ">")
    .replace(/&amp;/g, "&")
    .replace(/&#39;/g, "'")
    .replace(/&quot;/g, '"');

  return (
    /(^|\n)\s*(\$|sudo\s|git\s|python3?\s|pip\s|npm\s|pnpm\s|yarn\s|llama-|ollama\s|curl\s|wget\s|apt\s|cd\s|ls\s|cat\s|cp\s|mv\s|chmod\s|make\s)/i.test(
      codeText,
    ) || /--[a-z0-9-]+/i.test(codeText)
  );
}

function commandLinesToHtml(codeHtml: string) {
  const lines = codeHtml
    .split("\n")
    .map((line) => line.trimEnd())
    .filter((line) => line.length > 0);

  if (lines.length === 0) {
    return `<span class="cmd-pill">${codeHtml}</span>`;
  }

  return lines.map((line) => `<span class="cmd-pill">${line}</span>`).join("\n");
}

function markExplicitInstructionElements(
  html: string,
  options?: {
    commandPills?: boolean;
  },
) {
  const renderCommandPills = options?.commandPills ?? false;
  const imperativeLead =
    /^(?:\d+\.\s*)?(?:open|go to|navigate to|click|select|run|enter|type|copy|paste|create|clone|convert|inspect|execute|use|download|install|review|confirm|kill|rerun|quit|start|stop)\b/i;

  let markedHtml = html.replace(
    /<pre([^>]*)>\s*<code([^>]*)>([\s\S]*?)<\/code>\s*<\/pre>/gi,
    (fullMatch, rawPreAttrs: string, rawCodeAttrs: string, codeHtml: string) => {
      if (!looksLikeCommandBlock(codeHtml)) return fullMatch;

      const preAttrs = addClassAttribute(rawPreAttrs.trim(), "explicit-command-block");
      const codeAttrs = addClassAttribute(rawCodeAttrs.trim(), "explicit-command");
      const commandContent = renderCommandPills ? commandLinesToHtml(codeHtml) : codeHtml;

      return `<pre${preAttrs ? ` ${preAttrs}` : ""}><code${codeAttrs ? ` ${codeAttrs}` : ""}>${commandContent}</code></pre>`;
    },
  );

  markedHtml = markedHtml.replace(
    /<(p|li)([^>]*)>([\s\S]*?)<\/\1>/gi,
    (fullMatch, tagName: string, rawAttrs: string, innerHtml: string) => {
      const plainText = extractPlainText(innerHtml);
      if (!imperativeLead.test(plainText)) return fullMatch;

      const attrs = addClassAttribute(rawAttrs.trim(), "explicit-instruction");
      return `<${tagName}${attrs ? ` ${attrs}` : ""}>${innerHtml}</${tagName}>`;
    },
  );

  return markedHtml;
}

function classifyStepKind(sectionHtml: string, mode: string | null) {
  const hasExplicitInstruction = /class="[^"]*\bexplicit-instruction\b[^"]*"/i.test(sectionHtml);
  const hasCommandBlock = /class="[^"]*\bexplicit-command-block\b[^"]*"/i.test(sectionHtml);
  const hasOrderedList = /<ol>/i.test(sectionHtml);

  if (mode === "execute" || mode === "checkpoint") return "instruction";
  if (mode === "explore" || mode === "review") {
    return hasCommandBlock ? "instruction" : "explanation";
  }

  if (hasCommandBlock || hasExplicitInstruction) return "instruction";
  if (hasOrderedList) return "mixed";
  return "explanation";
}

function findObjectiveSegmentOpenStart(html: string, headingIndex: number) {
  const lookbackStart = Math.max(0, headingIndex - 120);
  const beforeHeading = html.slice(lookbackStart, headingIndex);
  const openTagMatch = /<section class="objective-segment">\s*$/i.exec(beforeHeading);

  if (!openTagMatch) return headingIndex;
  return headingIndex - openTagMatch[0].length;
}

function segmentStepSections(html: string) {
  const headingPattern = /<h([2-4])([^>]*)>([\s\S]*?)<\/h\1>/gi;
  const headings: Array<{
    index: number;
    level: number;
    attrs: string;
    text: string;
    isStep: boolean;
  }> = [];
  let match = headingPattern.exec(html);

  while (match) {
    const level = Number(match[1] ?? "0");
    const attrs = match[2] ?? "";
    const text = match[3] ?? "";
    const hasStepClass = /\bclass\s*=\s*"[^"]*\blab-step-title\b/i.test(attrs);
    const isObjectiveLevelTwo = level === 2 && isObjectiveHeading(text);
    const supportsStepSegmentation = level >= 3 || (level === 2 && !isObjectiveLevelTwo);
    headings.push({
      index: match.index,
      level,
      attrs,
      text,
      isStep: supportsStepSegmentation && (hasStepClass || isStepHeading(text)),
    });
    match = headingPattern.exec(html);
  }

  if (headings.length === 0) return html;

  let output = "";
  let cursor = 0;

  for (let i = 0; i < headings.length; i++) {
    const heading = headings[i];
    if (!heading.isStep) continue;

    let nextIndex = html.length;
    for (let j = i + 1; j < headings.length; j++) {
      const nextHeading = headings[j];
      if (nextHeading.level === 2 || nextHeading.isStep) {
        nextIndex = findObjectiveSegmentOpenStart(html, nextHeading.index);
        break;
      }
    }

    const sectionHtml = html.slice(heading.index, nextIndex);
    const modeFromAttrs = /data-step-mode\s*=\s*"([^"]+)"/i.exec(heading.attrs)?.[1] ?? null;
    const mode = modeFromAttrs ?? getStepMode(heading.text);
    const kind = classifyStepKind(sectionHtml, mode);
    const modeAttribute = mode ? ` data-step-mode="${mode}"` : "";

    output += html.slice(cursor, heading.index);
    output += `<section class="step-segment" data-step-kind="${kind}"${modeAttribute}>${sectionHtml}</section>`;
    cursor = nextIndex;
  }

  output += html.slice(cursor);
  return output;
}

function addClassAttribute(attrs: string, className: string) {
  const classPattern = /class\s*=\s*"([^"]*)"/i;
  if (classPattern.test(attrs)) {
    return attrs.replace(classPattern, (_, classList: string) => {
      const classes = classList.split(/\s+/).filter(Boolean);
      if (!classes.includes(className)) classes.push(className);
      return `class="${classes.join(" ")}"`;
    });
  }
  return `${attrs} class="${className}"`.trim();
}

function addDataAttribute(attrs: string, name: string, value: string) {
  const attrPattern = new RegExp(`\\b${name}\\s*=`, "i");
  if (attrPattern.test(attrs)) return attrs;
  return `${attrs} ${name}="${value}"`.trim();
}

function stripStepOrdinalPrefix(headingHtml: string) {
  return headingHtml.replace(/^(\s*)(?:\d+(?:\.\d+)*(?:[.):])?\s+)/, "$1");
}

function annotateStepHeadings(
  html: string,
  options?: {
    stripOrdinals?: boolean;
  },
) {
  const stripOrdinals = options?.stripOrdinals ?? false;

  return html.replace(
    /<h([2-4])([^>]*)>([\s\S]*?)<\/h\1>/gi,
    (fullMatch, level: string, rawAttrs: string, headingHtml: string) => {
      const numericLevel = Number(level);
      if (numericLevel === 2 && isObjectiveHeading(headingHtml)) return fullMatch;
      if (!isStepHeading(headingHtml)) return fullMatch;

      let attrs = rawAttrs.trim();
      attrs = addClassAttribute(attrs, "lab-step-title");

      const mode = getStepMode(headingHtml);
      if (mode) {
        attrs = addDataAttribute(attrs, "data-step-mode", mode);
      }

      const displayHeadingHtml = stripOrdinals
        ? stripStepOrdinalPrefix(headingHtml)
        : headingHtml;

      return `<h${level}${attrs ? ` ${attrs}` : ""}>${displayHeadingHtml}</h${level}>`;
    },
  );
}

function splitTableRow(line: string) {
  let row = line.trim();
  if (row.startsWith("|")) row = row.slice(1);
  if (row.endsWith("|")) row = row.slice(0, -1);
  return row.split("|").map((cell) => cell.trim());
}

function isTableDivider(line: string) {
  const cells = splitTableRow(line);
  return cells.length > 0 && cells.every((cell) => /^:?-{3,}:?$/.test(cell));
}

function isTableRow(line: string) {
  if (!line.includes("|")) return false;
  return splitTableRow(line).length > 1;
}

function tableAlignment(dividerCell: string) {
  if (/^:-+:$/.test(dividerCell)) return ' style="text-align:center;"';
  if (/^-+:$/.test(dividerCell)) return ' style="text-align:right;"';
  return "";
}

function renderInlineMarkdown(markdown: string) {
  const html = micromark(markdown, { allowDangerousHtml: false }).trim();
  if (html.startsWith("<p>") && html.endsWith("</p>")) {
    return html.slice(3, -4);
  }
  return html;
}

function convertGfmTables(markdown: string) {
  const lines = markdown.split("\n");
  const out: string[] = [];
  let inFence = false;

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i] ?? "";
    const fenceMatch = /^(```|~~~)/.exec(line.trim());
    if (fenceMatch) {
      inFence = !inFence;
      out.push(line);
      continue;
    }

    if (inFence) {
      out.push(line);
      continue;
    }

    const next = lines[i + 1];
    if (next && isTableRow(line) && isTableDivider(next)) {
      const header = splitTableRow(line);
      const divider = splitTableRow(next);
      const rows: string[][] = [];

      let j = i + 2;
      while (j < lines.length && isTableRow(lines[j] ?? "") && !isTableDivider(lines[j] ?? "")) {
        rows.push(splitTableRow(lines[j]!));
        j++;
      }

      const alignAttrs = header.map((_, idx) => tableAlignment(divider[idx] ?? ""));
      const headHtml = header
        .map((cell, idx) => `<th${alignAttrs[idx] ?? ""}>${renderInlineMarkdown(cell)}</th>`)
        .join("");
      const bodyHtml = rows
        .map((row) => {
          const cells = header.map((_, idx) => renderInlineMarkdown(row[idx] ?? ""));
          return `<tr>${cells.map((cell, idx) => `<td${alignAttrs[idx] ?? ""}>${cell}</td>`).join("")}</tr>`;
        })
        .join("");

      out.push("<table>");
      out.push(`<thead><tr>${headHtml}</tr></thead>`);
      out.push(`<tbody>${bodyHtml}</tbody>`);
      out.push("</table>");

      i = j - 1;
      continue;
    }

    out.push(line);
  }

  return out.join("\n");
}

function markdownToHtml(markdown: string) {
  return micromark(convertGfmTables(markdown), { allowDangerousHtml: true });
}

export async function generateStaticParams() {
  return getLabSummaries().map((lab) => ({ slug: lab.slug }));
}

export default async function LabPage({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  const lab = getLabDocument(slug);
  if (!lab) {
    return notFound();
  }

  const { content, data } = lab;
  const { markdown: markdownWithoutObjectiveDirective, style: objectiveDirectiveStyle } =
    extractObjectiveStyleDirective(content);
  const { markdown: markdownWithoutStepDirective, style: stepDirectiveStyle } =
    extractStepStyleDirective(markdownWithoutObjectiveDirective);
  const { markdown, style: breakoutDirectiveStyle } = extractBreakoutStyleDirective(
    markdownWithoutStepDirective,
  );
  const styleConfig = data as {
    objectiveStyle?: unknown;
    stepStyle?: unknown;
    breakoutStyle?: unknown;
  };
  const objectiveStyle =
    normalizeObjectiveStyle(styleConfig.objectiveStyle) ?? objectiveDirectiveStyle ?? "divider";
  const stepStyle = normalizeStepStyle(styleConfig.stepStyle) ?? stepDirectiveStyle ?? "underline";
  const breakoutStyle =
    normalizeBreakoutStyle(styleConfig.breakoutStyle) ?? breakoutDirectiveStyle ?? "none";
  const objectiveSegmentedHtml = segmentObjectiveSections(
    stripObjectiveDividers(markdownToHtml(markdown)),
  );
  const baseHtml = transformOutsideDetails(objectiveSegmentedHtml, (safeHtml) =>
    annotateStepHeadings(safeHtml, {
      stripOrdinals: breakoutStyle === "instruction-rails",
    }),
  );
  const htmlContent =
    breakoutStyle === "none"
      ? baseHtml
      : transformOutsideDetails(baseHtml, (safeHtml) =>
          segmentStepSections(markExplicitInstructionElements(safeHtml, {
            commandPills: breakoutStyle === "command-pills",
          })),
        );

  return (
    <main className="mx-auto w-full max-w-5xl px-6 py-10">
      <div className="mx-auto max-w-4xl rounded-lg border border-[#f6d5a5] bg-white p-6 md:p-8">
        <h1 className="mb-6 text-3xl font-bold text-[#004E78]">{lab.title}</h1>
        <LabContent
          className={`lab-content max-w-none objective-style-${objectiveStyle} step-style-${stepStyle} breakout-style-${breakoutStyle}`}
          html={htmlContent}
        />
      </div>
    </main>
  );
}
