import {
  makeStyleDeclarationFact,
  type Fact,
  type FactLocation,
  type StyleDeclarationFact,
} from "@fragments-sdk/core";

import { extractScssFacts } from "./scss.js";

const STYLE_BLOCK_PATTERN = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;

export function extractStyleBlockFacts(source: string, filePath: string): Fact[] {
  const lineStarts = buildLineStarts(source);
  const facts: Fact[] = [];
  let blockIndex = 0;

  for (const match of source.matchAll(STYLE_BLOCK_PATTERN)) {
    const fullMatch = match[0];
    const css = match[1] ?? "";
    const openingEnd = fullMatch.indexOf(">") + 1;
    const cssStart = match.index + openingEnd;
    const blockStart = lineColFromOffset(lineStarts, cssStart);

    facts.push(
      ...extractScssFacts(css, filePath)
        .filter((fact): fact is StyleDeclarationFact => fact.kind === "style_declaration")
        .map((fact) =>
          makeStyleDeclarationFact({
            file: filePath,
            selector: `style-block:${blockIndex}:${fact.selector}`,
            declarationPath: `${blockIndex}/${fact.declarationPath}`,
            property: fact.property,
            value: fact.value,
            location: offsetBlockLocation(fact.location, blockStart),
          }),
        ),
    );
    blockIndex += 1;
  }

  return facts;
}

function offsetBlockLocation(
  location: FactLocation,
  blockStart: { line: number; column: number },
): FactLocation {
  return {
    file: location.file,
    line: blockStart.line + location.line - 1,
    column:
      location.line === 1
        ? blockStart.column + location.column
        : location.column,
  };
}

function buildLineStarts(content: string): number[] {
  const starts = [0];
  for (let i = 0; i < content.length; i++) {
    if (content.charCodeAt(i) === 10) starts.push(i + 1);
  }
  return starts;
}

function lineColFromOffset(
  lineStarts: readonly number[],
  offset: number,
): { line: number; column: number } {
  let lo = 0;
  let hi = lineStarts.length - 1;
  while (lo < hi) {
    const mid = (lo + hi + 1) >>> 1;
    if (lineStarts[mid] <= offset) lo = mid;
    else hi = mid - 1;
  }
  return { line: lo + 1, column: offset - lineStarts[lo] };
}
