//* 편집 경로 모양 거름만 한다(명세 4.9 절 정규식표). 적용·값 형식 검사·파생값 재계산은 워커 revalidate 가 한다.
const COMMON_REPLACE = [/^\/data\/subject\/(name|birth)$/];

const RULES: Record<string, { replace: RegExp[]; add: RegExp[] }> = {
  CREDIT_INFO: {
    replace: [
      /^\/data\/silson_rows\/\d+\/(insurer|start|end|amount|scope|feature|medical|self_pay_pct|status|excluded)$/,
      /^\/data\/fixed_detail\/\d+\/(insurer|product|start|end|pay_years|pay_cycle|premium|coverage_raw|standard|amount|status|renewable|excluded)$/,
      /^\/data\/fixed_summary\/\d+\/(standard|count|total|nonlife|life|mutual|excluded)$/,
    ],
    add: [/^\/data\/(silson_rows|fixed_detail)\/-$/],
  },
  STATEMENT: {
    replace: [
      /^\/data\/(insurer|issued_date)$/,
      /^\/data\/insured\/name$/,
      /^\/data\/doc_scope\/(kind|pages_complete|contract_scope|period\/start|period\/end)$/,
      /^\/data\/(contracts|claims|payments|visits)\/\d+\/(?!row_id$|src$)[a-z_]+$/,
    ],
    add: [/^\/data\/(contracts|claims|payments|visits)\/-$/],
  },
  NTS: {
    replace: [
      /^\/data\/(insurance_rows|medical_rows|silson_receipts)\/\d+\/(?!row_id$|src$)[a-z_]+$/,
      /^\/data\/printed_totals\/\d+\/amount$/, //* 인쇄 합계를 못 읽거나 잘못 읽었을 때 원문을 보고 고친다
    ],
    add: [/^\/data\/(insurance_rows|medical_rows|silson_receipts)\/-$/],
  },
  //* 보장 목록(AI 읽기) — 피보험자 이름 · 담보 행만. 이름이 달라 분석에서 빠진 문서를 파일 화면에서 푼다
  COVERAGE: {
    replace: [
      /^\/data\/insured\/name$/,
      /^\/data\/silson_rows\/\d+\/(insurer|start|end|amount|status|excluded)$/,
      /^\/data\/fixed_detail\/\d+\/(insurer|product|start|end|coverage_raw|standard|amount|status|excluded)$/,
    ],
    add: [],
  },
  //* 약관은 연결할 통합조회 실손 계약(보험사·시작일)만 고친다 — 조항 값은 원문 근거라 편집 대상이 아니다
  POLICY: { replace: [/^\/data\/link\/(insurer|start)$/], add: [] },
  HIRA: {
    replace: [
      /^\/data\/query_period\/(start|end)$/,
      /^\/data\/rows\/\d+\/(?!row_id$|src$|extra$)[a-z_]+$/,
    ],
    add: [/^\/data\/rows\/-$/],
  },
};

export const MAX_EDITS = 500;
export const MAX_EDIT_VALUE_BYTES = 10 * 1024;

//* 통과하면 null, 아니면 이유 코드
export function editRejection(
  kind: string | null | undefined,
  edit: { op?: unknown; path?: unknown; value?: unknown },
): string | null {
  const rules = kind?.startsWith('HIRA_') ? RULES.HIRA : RULES[kind ?? ''];
  if (!rules) return 'KIND_NOT_EDITABLE';
  if (edit.op !== 'replace' && edit.op !== 'add') return 'OP_INVALID';
  if (typeof edit.path !== 'string') return 'PATH_INVALID';
  const list =
    edit.op === 'add' ? rules.add : [...COMMON_REPLACE, ...rules.replace];
  if (!list.some((re) => re.test(edit.path as string)))
    return 'PATH_NOT_ALLOWED';
  if (
    Buffer.byteLength(JSON.stringify(edit.value ?? null)) > MAX_EDIT_VALUE_BYTES
  )
    return 'VALUE_TOO_LARGE';
  return null;
}
