// The shared chart spine — Task 1 of
// docs/superpowers/plans/2026-08-02-chart-templates.md. Extracted from the
// original small-business starter chart so it is defined exactly ONCE and
// spread into every industry template: the system accounts can never drift
// apart between templates (spec: "spine present and byte-identical across
// all eight").
//
// What belongs in the spine (and why):
//   - Accounts a system feature auto-finds: Accounts Receivable 1100
//     (ensure_ar_account adopts by name), Accounts Payable 2000
//     (ensure_ap_account), Sales Tax Payable 2200 (enable_sales_tax adopts
//     the one active liability with that name; 2200 is the convention 0015
//     documents), Payroll Liabilities 2300 + Payroll Wages 6050 + Payroll
//     Taxes 6060 (ADP payroll_code_map targets, CLAUDE.md §8).
//   - Equity: Owner's Equity 3000 doubles as the contribution /
//     retained-earnings-equivalent account and Owner's Distributions 3010 is
//     the draw — the same two accounts the original chart shipped (the
//     general template must reproduce that chart exactly, so the spine
//     cannot invent new equity accounts).
//   - The genuinely universal asset/liability/expense set the original chart
//     curated: every business has some mix of bank accounts, prepaids, fixed
//     assets, a credit card, a loan, rent, utilities, insurance, and so on.
//     QuickBooks ships this same core in every one of its industry charts.
//
// What does NOT belong: income accounts other than "Other Income" 4020 —
// naming the money coming IN is exactly where industries differ, so each
// template defines its own 4xxx accounts.
//
// Code ranges (mirrors the original chart's QB-compatible buckets — 1000s
// assets, 2000s liabilities, 3000s equity, 4000s income, 6000s expenses,
// step 10 so users can slot accounts in between):
//   spine:    1000-1290 + 1500-1590 assets, 2000-2490 liabilities,
//             3000-3090 equity, 4020 income, 6000-6290 expenses
//   industry: see INDUSTRY_CODE_RANGES below — 1300-1490 assets, 2500-2990
//             liabilities, 4000-4010 + 4030-4990 income, 5000-5990 direct
//             costs / cost of goods sold (the schema's CHECK allows exactly
//             five kinds — asset, liability, equity, income, expense
//             (db/migrations/0002) — so COGS-style accounts are `expense`
//             rows in the 5xxx bucket, QB-convention), 6300-7990 expenses.

import type { TemplateAccount } from "./types.js";

/**
 * The code ranges industry (non-spine) accounts may occupy — the single
 * source of truth for the range scheme described above, exported so the
 * structural test suite asserts range membership against THIS list rather
 * than a copy that could drift from the docs. Codes are 4-digit strings, so
 * inclusive string comparison is numeric comparison. Everything outside
 * these ranges is reserved for the spine and its future expansion.
 */
export const INDUSTRY_CODE_RANGES: ReadonlyArray<readonly [low: string, high: string]> =
  Object.freeze([
    ["1300", "1490"], // industry assets (inventory, retainage, trust, buildings...)
    ["2500", "2990"], // industry liabilities (tips, deposits held, retainers...)
    ["4000", "4010"], // primary income accounts (4020 Other Income is spine)
    ["4030", "4990"], // further income accounts
    ["5000", "5990"], // direct costs / COGS-style expense accounts
    ["6300", "7990"], // industry expenses (6000-6290 is the spine expense block)
  ]);

/** True when a 4-digit account code falls inside an industry range. */
export function isIndustryCode(code: string): boolean {
  return INDUSTRY_CODE_RANGES.some(([low, high]) => code >= low && code <= high);
}

// Subtypes (docs/superpowers/specs/2026-08-02-chart-management-design.md
// Part 1, migration 0016): presentation-and-grouping refinements of the five
// types. Choices of note: Cash on Hand/Checking/Savings are all `bank` (they
// are money accounts — the reconcile/register flows treat them alike);
// Accumulated Depreciation is `fixed-asset` (contra to the assets it offsets,
// grouped beside them, QB-convention); Sales Tax Payable and Payroll
// Liabilities are `other-current-liability`; Loan Payable is
// `long-term-liability`.
export const SPINE_ACCOUNTS: readonly TemplateAccount[] = Object.freeze([
  // --- Assets (1000s): bank/cash, A/R, fixed assets + accumulated depreciation ---
  { code: "1000", name: "Cash on Hand", type: "asset", subtype: "bank" },
  { code: "1010", name: "Checking Account", type: "asset", subtype: "bank" },
  { code: "1020", name: "Savings Account", type: "asset", subtype: "bank" },
  { code: "1100", name: "Accounts Receivable", type: "asset", subtype: "accounts-receivable" },
  { code: "1200", name: "Prepaid Expenses", type: "asset", subtype: "other-current-asset" },
  { code: "1500", name: "Furniture and Equipment", type: "asset", subtype: "fixed-asset" },
  { code: "1510", name: "Vehicles", type: "asset", subtype: "fixed-asset" },
  { code: "1590", name: "Accumulated Depreciation", type: "asset", subtype: "fixed-asset" },

  // --- Liabilities (2000s): A/P, credit card, sales tax payable, payroll, loan ---
  { code: "2000", name: "Accounts Payable", type: "liability", subtype: "accounts-payable" },
  { code: "2100", name: "Credit Card", type: "liability", subtype: "credit-card" },
  { code: "2200", name: "Sales Tax Payable", type: "liability", subtype: "other-current-liability" },
  { code: "2300", name: "Payroll Liabilities", type: "liability", subtype: "other-current-liability" },
  { code: "2400", name: "Loan Payable", type: "liability", subtype: "long-term-liability" },

  // --- Equity (3000s): owner equity + distributions ---
  { code: "3000", name: "Owner's Equity", type: "equity", subtype: "equity" },
  { code: "3010", name: "Owner's Distributions", type: "equity", subtype: "equity" },

  // --- Income: only the catch-all; primary income accounts are per-industry ---
  { code: "4020", name: "Other Income", type: "income", subtype: "other-income" },

  // --- Expense (6000s): the universal small-business expense set ---
  { code: "6000", name: "Advertising", type: "expense", subtype: "expense" },
  { code: "6010", name: "Bank Fees", type: "expense", subtype: "expense" },
  { code: "6020", name: "Insurance", type: "expense", subtype: "expense" },
  { code: "6030", name: "Meals and Entertainment", type: "expense", subtype: "expense" },
  { code: "6040", name: "Office Supplies", type: "expense", subtype: "expense" },
  { code: "6050", name: "Payroll Wages", type: "expense", subtype: "expense" },
  { code: "6060", name: "Payroll Taxes", type: "expense", subtype: "expense" },
  { code: "6070", name: "Professional Fees", type: "expense", subtype: "expense" },
  { code: "6080", name: "Rent", type: "expense", subtype: "expense" },
  { code: "6090", name: "Repairs and Maintenance", type: "expense", subtype: "expense" },
  { code: "6100", name: "Software Subscriptions", type: "expense", subtype: "expense" },
  { code: "6110", name: "Phone and Internet", type: "expense", subtype: "expense" },
  { code: "6120", name: "Travel", type: "expense", subtype: "expense" },
  { code: "6130", name: "Utilities", type: "expense", subtype: "expense" },
  { code: "6140", name: "Vehicle Expenses", type: "expense", subtype: "expense" },
  { code: "6150", name: "Contract Labor", type: "expense", subtype: "expense" },
  { code: "6160", name: "Dues and Subscriptions", type: "expense", subtype: "expense" },
  { code: "6170", name: "Postage and Shipping", type: "expense", subtype: "expense" },
  { code: "6180", name: "Taxes and Licenses", type: "expense", subtype: "expense" },
  { code: "6190", name: "Continuing Education", type: "expense", subtype: "expense" },
  { code: "6200", name: "Depreciation Expense", type: "expense", subtype: "expense" },
  { code: "6210", name: "Miscellaneous Expense", type: "expense", subtype: "expense" },
]);

/**
 * Spine + industry accounts, sorted by code, frozen. Throws at module load
 * if a template's industry accounts collide with the spine (or each other)
 * on code or name — a template definition bug should fail the whole server
 * boot and every test run, not silently ship a broken chart.
 */
export function composeChart(industry: readonly TemplateAccount[]): readonly TemplateAccount[] {
  const all = [...SPINE_ACCOUNTS, ...industry];
  const codes = new Set<string>();
  const names = new Set<string>();
  for (const account of all) {
    if (codes.has(account.code)) {
      throw new Error(`chart template bug: duplicate account code ${account.code}`);
    }
    if (names.has(account.name)) {
      throw new Error(`chart template bug: duplicate account name ${account.name}`);
    }
    codes.add(account.code);
    names.add(account.name);
  }
  // Codes are all 4-digit strings, so a plain string compare sorts numerically.
  all.sort((a, b) => (a.code < b.code ? -1 : a.code > b.code ? 1 : 0));
  return Object.freeze(all);
}
