import { analyzeModel } from "./analyze.js";
import { DEFAULT_LAYOUT_ENGINE, layoutAndRoute, requestedLayoutEngine } from "./layout.js";
import { renderSvgFromLayout } from "./render.js";
import { validateModel } from "./validate.js";

function emptyTopology() {
  return {
    power_domains: [],
    clock_sources: [],
    signal_paths: []
  };
}

function emptyLayout() {
  return {
    width: 0,
    height: 0,
    placed: []
  };
}

function issueSuggestion(code) {
  const map = {
    ground_net_missing: "Add a GND net and connect all ground/reference pins to it.",
    multi_power_out: "Split this net or insert power management so only one source drives the net.",
    output_conflict: "Avoid direct output-to-output connection. Insert a buffer/selector or separate nets.",
    required_power_unconnected: "Connect this pin to the appropriate power or ground domain.",
    floating_input: "Drive this input from a defined source or add pull-up/pull-down network.",
    unknown_ref_in_net: "Fix net node reference to an existing component instance.",
    unknown_pin_in_net: "Fix net node pin name to match the selected symbol pin list.",
    auto_template_symbol_created:
      "A common component template was auto-selected (resistor/capacitor/etc). Replace with a custom symbol if needed.",
    auto_template_symbol_hydrated:
      "Template symbol fields were auto-filled. You can keep the short form or expand the symbol explicitly.",
    auto_generic_symbol_created:
      "A generic symbol was auto-created from net usage. You can replace it with a library symbol later.",
    auto_generic_symbol_hydrated:
      "Generic symbol fields were auto-filled from connectivity so minimal JSON remains valid.",
    auto_generic_pin_created:
      "A missing generic pin was inferred from net usage. Rename/reposition pins in symbols for cleaner diagrams.",
    auto_symbol_id_filled:
      "symbol_id was inferred from the symbol map key to keep the schema concise.",
    auto_symbol_category_filled:
      "category was inferred automatically. Set it explicitly if you need strict semantic grouping.",
    invalid_part_type:
      "Use a supported built-in part type or provide a full custom symbol definition.",
    instance_symbol_or_part_missing:
      "Each instance must define either 'symbol' (custom symbol) or 'part' (built-in shorthand)."
  };

  return map[code] ?? "Review this issue and adjust net connectivity or symbol definitions.";
}

function parseNetFromIssue(issue) {
  if (typeof issue.path === "string") {
    const m = /^nets\.([^\.]+)/.exec(issue.path);
    if (m) {
      return m[1];
    }
  }

  const m2 = /Net '([^']+)'/.exec(issue.message);
  if (m2) {
    return m2[1];
  }

  return null;
}

function parseRefFromIssue(issue) {
  if (typeof issue.path === "string") {
    const m = /^instances\.([^\.]+)/.exec(issue.path);
    if (m) {
      return m[1];
    }
  }

  const m2 = /'([A-Za-z][A-Za-z0-9_]*)\./.exec(issue.message);
  if (m2) {
    return m2[1];
  }

  return null;
}

function parseRefPinFromIssue(issue) {
  const m = /'([A-Za-z][A-Za-z0-9_]*)\.([A-Za-z0-9_]+)'/.exec(issue.message);
  if (m) {
    return { ref: m[1], pin: m[2] };
  }
  return null;
}

function pinPoint(inst, pin, width, height) {
  const x0 = inst.placement.x;
  const y0 = inst.placement.y;

  switch (pin.side) {
    case "left":
      return { x: x0, y: y0 + pin.offset };
    case "right":
      return { x: x0 + width, y: y0 + pin.offset };
    case "top":
      return { x: x0 + pin.offset, y: y0 };
    case "bottom":
      return { x: x0 + pin.offset, y: y0 + height };
    default:
      return { x: x0, y: y0 };
  }
}

function bboxFromPoints(points, padding = 18) {
  if (!points.length) {
    return null;
  }

  const xs = points.map((p) => p.x);
  const ys = points.map((p) => p.y);
  const minX = Math.min(...xs) - padding;
  const maxX = Math.max(...xs) + padding;
  const minY = Math.min(...ys) - padding;
  const maxY = Math.max(...ys) + padding;

  return {
    x: minX,
    y: minY,
    w: maxX - minX,
    h: maxY - minY
  };
}

function netFocusBbox(layout, netName) {
  const routed = layout.routed.find((r) => r.net.name === netName);
  if (!routed) {
    return null;
  }

  const points = [];
  for (const route of routed.routes) {
    for (const seg of route) {
      points.push(seg.a, seg.b);
    }
  }
  points.push(...(routed.tiePoints ?? []));
  points.push(...(routed.junctionPoints ?? []));
  points.push(...(routed.labelPoints ?? []));

  return bboxFromPoints(points);
}

function componentFocus(layout, model, ref) {
  const inst = layout.placed.find((x) => x.ref === ref);
  if (!inst) {
    return null;
  }
  const sym = model.symbols[inst.symbol];
  if (!sym) {
    return null;
  }

  return {
    type: "component",
    ref,
    bbox: {
      x: inst.placement.x - 12,
      y: inst.placement.y - 12,
      w: sym.body.width + 24,
      h: sym.body.height + 24
    }
  };
}

function pinFocus(layout, model, ref, pinName) {
  const inst = layout.placed.find((x) => x.ref === ref);
  if (!inst) {
    return null;
  }

  const sym = model.symbols[inst.symbol];
  const pin = sym?.pins.find((p) => p.name === pinName);
  if (!pin) {
    return null;
  }

  const p = pinPoint(inst, pin, sym.body.width, sym.body.height);
  return {
    type: "pin",
    ref,
    pin: pinName,
    bbox: { x: p.x - 28, y: p.y - 28, w: 56, h: 56 }
  };
}

function buildFocusMap(model, layout, issues) {
  const map = {};

  for (const issue of issues) {
    const byNet = parseNetFromIssue(issue);
    if (byNet) {
      map[issue.id] = {
        type: "net",
        net: byNet,
        bbox: netFocusBbox(layout, byNet)
      };
      continue;
    }

    const rp = parseRefPinFromIssue(issue);
    if (rp) {
      const pf = pinFocus(layout, model, rp.ref, rp.pin);
      if (pf) {
        map[issue.id] = pf;
        continue;
      }
    }

    const byRef = parseRefFromIssue(issue);
    if (byRef) {
      const cf = componentFocus(layout, model, byRef);
      if (cf) {
        map[issue.id] = cf;
        continue;
      }
    }

    map[issue.id] = { type: "global", bbox: { x: 0, y: 0, w: layout.width, h: layout.height } };
  }

  return map;
}

function annotateIssues(issues, prefix) {
  return issues.map((issue, idx) => ({
    ...issue,
    id: `${prefix}${idx}`,
    suggestion: issueSuggestion(issue.code)
  }));
}

export function compile(payload, options = {}) {
  const validated = validateModel(payload, options);
  const layoutEngineRequested = requestedLayoutEngine(options);

  if (!validated.model) {
    const errors = annotateIssues(validated.issues.filter((x) => x.severity === "error"), "E");
    const warnings = annotateIssues(validated.issues.filter((x) => x.severity === "warning"), "W");
    return {
      ok: false,
      errors,
      warnings,
      topology: emptyTopology(),
      layout: emptyLayout(),
      layout_metrics: {
        segment_count: 0,
        overlap_edges: 0,
        crossings: 0,
        label_collisions: 0,
        tie_points_used: 0,
        bus_groups: 0,
        total_bends: 0,
        total_length: 0,
        direct_length: 0,
        detour_ratio: 1,
        label_tie_fallbacks: 0,
        label_tie_routes: 0
      },
      bus_groups: [],
      focus_map: {},
      render_mode_used: options.render_mode ?? "schematic_stub",
      layout_engine_requested: layoutEngineRequested,
      layout_engine_used: DEFAULT_LAYOUT_ENGINE,
      layout_warnings: [],
      svg: ""
    };
  }

  const analysis = analyzeModel(validated.model, validated.issues);
  const layout = layoutAndRoute(validated.model, options);
  const svg = renderSvgFromLayout(validated.model, layout, options);

  const errors = annotateIssues(analysis.errors, "E");
  const warnings = annotateIssues(analysis.warnings, "W");
  const issues = [...errors, ...warnings];
  const focusMap = buildFocusMap(validated.model, layout, issues);

  const placed = layout.placed.map((inst) => ({
    ref: inst.ref,
    x: inst.placement.x,
    y: inst.placement.y,
    rotation: inst.placement.rotation,
    locked: inst.placement.locked
  }));

  return {
    ...analysis,
    errors,
    warnings,
    focus_map: focusMap,
    layout: {
      width: layout.width,
      height: layout.height,
      placed
    },
    layout_metrics: layout.metrics,
    bus_groups: layout.bus_groups,
    render_mode_used: layout.render_mode_used,
    layout_engine_requested: layout.layout_engine_requested ?? layoutEngineRequested,
    layout_engine_used: layout.layout_engine_used ?? DEFAULT_LAYOUT_ENGINE,
    layout_warnings: Array.isArray(layout.layout_warnings) ? layout.layout_warnings : [],
    svg
  };
}

export function analyze(payload, options = {}) {
  const validated = validateModel(payload, options);

  if (!validated.model) {
    const errors = annotateIssues(validated.issues.filter((x) => x.severity === "error"), "E");
    const warnings = annotateIssues(validated.issues.filter((x) => x.severity === "warning"), "W");
    return {
      ok: false,
      errors,
      warnings,
      topology: emptyTopology()
    };
  }

  const out = analyzeModel(validated.model, validated.issues);
  return {
    ...out,
    errors: annotateIssues(out.errors, "E"),
    warnings: annotateIssues(out.warnings, "W")
  };
}
