'use strict';

const fs = require('node:fs');
const path = require('node:path');
const { sanitizeHtml } = require('./sanitize');
const { htmlToText, deepClone } = require('./util');
const { systemPlaceholders, resolveScopes, expandPlaceholders } = require('./placeholders');
const { decodePng } = require('./png');
const { renderAnnotations, applyFocusedView } = require('./raster');
const { orderedBlocks, blockText } = require('./blocks');

/**
 * The Render AST is the single normalized document model every exporter
 * consumes. It resolves placeholders, hierarchical numbering, hidden/skipped
 * filtering, and absolute image paths — exporters never read the store.
 */

function buildRenderAst(store, guideId, { globals = {}, now = new Date(), maxSteps = 0 } = {}) {
  const guide = store.getGuide(guideId);
  const stepsMap = store.listSteps(guideId);

  const includedIds = guide.stepsOrder.filter((id) => {
    const s = stepsMap.get(id);
    if (!s || s.hidden) return false;
    if (s.skipped && guide.flags.hideSkippedStepsInExports) return false;
    return true;
  });

  const values = resolveScopes({
    guide,
    globals,
    system: systemPlaceholders(guide, { now, stepCount: includedIds.length }),
  });
  const expand = (text) => expandPlaceholders(text, values);

  const steps = [];
  const topCounter = { n: 0 };
  const childCounters = new Map();
  const numberOf = new Map();

  for (const id of includedIds) {
    const raw = stepsMap.get(id);
    const step = deepClone(raw);
    let number;
    let depth = 0;
    if (step.parentStepId && numberOf.has(step.parentStepId)) {
      const parentNo = numberOf.get(step.parentStepId);
      const c = (childCounters.get(step.parentStepId) || 0) + 1;
      childCounters.set(step.parentStepId, c);
      number = `${parentNo}.${c}`;
      depth = number.split('.').length - 1;
    } else {
      step.parentStepId = null; // orphan substeps render top-level
      topCounter.n += 1;
      number = String(topCounter.n);
    }
    numberOf.set(step.stepId, number);

    const ast = {
      stepId: step.stepId,
      parentStepId: step.parentStepId,
      number,
      depth,
      kind: step.kind,
      status: step.status,
      skipped: step.skipped,
      forceNewPage: Boolean(step.forceNewPage),
      title: expand(step.title || ''),
      descriptionHtml: sanitizeHtml(expand(step.descriptionHtml || '')),
      descriptionText: htmlToText(expand(step.descriptionHtml || '')),
      focusedView: step.focusedView,
      annotations: (step.annotations || []).map((a) => ({ ...a, text: expand(a.text || '') })),
      textBlocks: (step.textBlocks || []).map((tb) => ({
        ...tb,
        title: expand(tb.title || ''),
        descriptionHtml: sanitizeHtml(expand(tb.descriptionHtml || '')),
        descriptionText: htmlToText(expand(tb.descriptionHtml || '')),
      })),
      codeBlocks: (step.codeBlocks || []).map((cb) => ({ ...cb, code: blockText(cb) })),
      tableBlocks: (step.tableBlocks || []).map((tb) => ({
        ...tb,
        rows: Array.isArray(tb.rows) ? tb.rows.map((row) => [...row]) : [],
      })),
      blocks: orderedBlocks(step).map((block) => {
        if (block.kind === 'text') {
          return {
            ...block,
            title: expand(block.title || ''),
            descriptionHtml: sanitizeHtml(expand(block.descriptionHtml || '')),
            descriptionText: htmlToText(expand(block.descriptionHtml || '')),
          };
        }
        if (block.kind === 'code') return { ...block };
        if (block.kind === 'table') return { ...block };
        return { ...block };
      }),
      links: step.links || [],
      image: null,
    };
    if (step.image) {
      const absPath = path.join(store.stepDir(guideId, step.stepId), step.image.workingPath);
      if (fs.existsSync(absPath)) {
        ast.image = { absPath, width: step.image.size.width, height: step.image.size.height };
      }
    }
    steps.push(ast);
  }

  const limited = maxSteps > 0 ? steps.slice(0, maxSteps) : steps;

  return {
    format: 'stepforge-render-ast',
    version: 1,
    generatedAt: now.toISOString(),
    placeholders: values,
    guide: {
      id: guide.guideId,
      title: expand(guide.title),
      descriptionHtml: sanitizeHtml(expand(guide.descriptionHtml || '')),
      descriptionText: htmlToText(expand(guide.descriptionHtml || '')),
      createdAt: guide.createdAt,
      updatedAt: guide.updatedAt,
      flags: guide.flags,
    },
    steps: limited,
  };
}

/**
 * Decode a step's working image and burn in annotations + focused view.
 * Returns an RGBA raster image, or null for steps without images.
 */
function renderStepImage(astStep) {
  if (!astStep.image) return null;
  const base = decodePng(fs.readFileSync(astStep.image.absPath));
  const annotated = renderAnnotations(base, astStep.annotations);
  return applyFocusedView(annotated, astStep.focusedView);
}

module.exports = { buildRenderAst, renderStepImage };
