function pinTypeFor(model, ref, pin) {
  const instance = model.instances.find((x) => x.ref === ref);
  if (!instance) {
    return "passive";
  }
  const symbol = model.symbols[instance.symbol];
  const p = symbol.pins.find((x) => x.name === pin);
  return p?.type ?? "passive";
}

function buildSignalPaths(model) {
  const directedEdges = [];

  for (const net of model.nets) {
    if (net.class === "power" || net.class === "ground") {
      continue;
    }

    const sources = net.nodes
      .filter((n) => {
        const t = pinTypeFor(model, n.ref, n.pin);
        return t === "output" || t === "power_out";
      })
      .map((n) => n.ref);

    const sinks = net.nodes
      .filter((n) => {
        const t = pinTypeFor(model, n.ref, n.pin);
        return t === "input" || t === "power_in" || t === "analog";
      })
      .map((n) => n.ref);

    for (const s of sources) {
      for (const d of sinks) {
        if (s !== d) {
          directedEdges.push([s, d]);
        }
      }
    }
  }

  const uniqEdges = new Map();
  for (const edge of directedEdges) {
    uniqEdges.set(`${edge[0]}->${edge[1]}`, edge);
  }

  const adj = new Map();
  for (const [a, b] of uniqEdges.values()) {
    const list = adj.get(a) ?? [];
    list.push(b);
    adj.set(a, list);
  }

  const starts = [...adj.keys()].sort();
  const result = [];

  for (const start of starts) {
    const queue = [{ node: start, path: [start] }];
    const seen = new Set();

    while (queue.length > 0) {
      const current = queue.shift();
      const key = current.path.join(">>");
      if (seen.has(key)) {
        continue;
      }
      seen.add(key);

      const next = adj.get(current.node) ?? [];
      if (next.length === 0 && current.path.length > 1) {
        result.push(current.path);
      }

      for (const n of next) {
        if (!current.path.includes(n)) {
          queue.push({ node: n, path: [...current.path, n] });
        }
      }
    }
  }

  const dedup = new Map();
  for (const p of result) {
    dedup.set(p.join(" -> "), p);
  }

  return [...dedup.values()].sort((a, b) => a.join("/").localeCompare(b.join("/")));
}

function detectBusGroups(model) {
  const groups = new Map();
  for (const net of model.nets) {
    const match = /^([A-Za-z0-9]+)_/.exec(net.name);
    if (!match) {
      continue;
    }
    const key = match[1].toUpperCase();
    const list = groups.get(key) ?? [];
    list.push(net.name);
    groups.set(key, list);
  }

  const out = [];
  for (const [name, nets] of groups.entries()) {
    if (nets.length >= 2) {
      out.push({ name, nets: nets.sort() });
    }
  }

  out.sort((a, b) => a.name.localeCompare(b.name));
  return out;
}

function extractTopology(model) {
  const powerNets = model.nets.filter((n) => n.class === "power" || n.class === "ground");
  const powerDomains = powerNets.map((n) => n.name).sort();

  const powerDomainConsumers = powerNets
    .map((net) => {
      const consumers = net.nodes
        .filter((n) => {
          const t = pinTypeFor(model, n.ref, n.pin);
          return t === "power_in" || t === "ground";
        })
        .map((n) => `${n.ref}.${n.pin}`)
        .sort();

      return {
        name: net.name,
        consumers,
        count: consumers.length
      };
    })
    .sort((a, b) => a.name.localeCompare(b.name));

  const clockSources = new Set();
  const clockSinks = new Set();

  for (const net of model.nets) {
    if (net.class !== "clock") {
      continue;
    }

    for (const node of net.nodes) {
      const type = pinTypeFor(model, node.ref, node.pin);
      if (type === "output" || type === "power_out") {
        clockSources.add(node.ref);
      } else if (type === "input" || type === "analog") {
        clockSinks.add(node.ref);
      }
    }
  }

  const buses = detectBusGroups(model);
  const signalPaths = buildSignalPaths(model);
  const namedSignalPaths = model.nets
    .filter((n) => n.class !== "power" && n.class !== "ground")
    .map((n) => {
      const nodes = n.nodes.map((x) => `${x.ref}.${x.pin}`);
      return {
        net: n.name,
        class: n.class,
        nodes
      };
    });

  return {
    power_domains: powerDomains,
    power_domain_consumers: powerDomainConsumers,
    clock_sources: [...clockSources].sort(),
    clock_sinks: [...clockSinks].sort(),
    buses,
    signal_paths: signalPaths,
    named_signal_paths: namedSignalPaths
  };
}

function ercChecks(model) {
  const issues = [];
  const hasGroundNet = model.nets.some((n) => n.class === "ground");

  if (!hasGroundNet) {
    issues.push({
      code: "ground_net_missing",
      message: "No ground net defined.",
      severity: "error",
      path: "nets"
    });
  }

  const connectedPins = new Set();

  for (const net of model.nets) {
    const types = net.nodes.map((n) => ({
      node: n,
      type: pinTypeFor(model, n.ref, n.pin)
    }));

    for (const t of types) {
      connectedPins.add(`${t.node.ref}.${t.node.pin}`);
    }

    const powerOut = types.filter((t) => t.type === "power_out");
    if (powerOut.length > 1) {
      issues.push({
        code: "multi_power_out",
        message: `Net '${net.name}' has multiple power_out pins.`,
        severity: "error",
        path: `nets.${net.name}`
      });
    }

    const outputs = types.filter((t) => t.type === "output");
    if (outputs.length > 1) {
      issues.push({
        code: "output_conflict",
        message: `Net '${net.name}' directly connects multiple output pins.`,
        severity: "error",
        path: `nets.${net.name}`
      });
    }
  }

  for (const instance of model.instances) {
    const symbol = model.symbols[instance.symbol];
    for (const pin of symbol.pins) {
      const key = `${instance.ref}.${pin.name}`;
      if ((pin.type === "power_in" || pin.type === "ground") && !connectedPins.has(key)) {
        issues.push({
          code: "required_power_unconnected",
          message: `Required pin '${key}' is unconnected.`,
          severity: "warning",
          path: `instances.${instance.ref}`
        });
      }
      if (pin.type === "input" && !connectedPins.has(key)) {
        issues.push({
          code: "floating_input",
          message: `Input pin '${key}' is floating.`,
          severity: "warning",
          path: `instances.${instance.ref}`
        });
      }
    }
  }

  return issues;
}

export function analyzeModel(model, validationIssues = []) {
  const ercIssues = ercChecks(model);
  const all = [...validationIssues, ...ercIssues];
  const errors = all.filter((x) => x.severity === "error");
  const warnings = all.filter((x) => x.severity === "warning");

  return {
    ok: errors.length === 0,
    errors,
    warnings,
    topology: extractTopology(model)
  };
}
