#!/usr/bin/env node
/**
 * Merge missing English (en) i18n keys.
 * Usage: node scripts/merge-en-translations.mjs
 */
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const root = path.join(__dirname, "..");
const localesDir = path.join(root, "src/i18n/locales");

/** @type {Record<string, Record<string, string>>} */
const translations = {
  "adminUsers.json": {
    roleListHint:
      "You can add custom roles and configure permissions. Built-in roles (super admin, site admin, site finance, site support, agent) cannot be deleted.",
    "rolePermissionDialog.packageHint":
      "Checking a module row on the left grants View only. Select Manage separately for data entry, close, draw, and other admin actions.",
    confirmSaveRolesTitle: "Save admin roles?",
    confirmSaveRolesDescription:
      "This updates role bindings for admin {{name}}; their console permissions will change accordingly.",
    confirmSaveAccountTitle: "Save admin account?",
    confirmSaveAccountCreateDescription: "This creates a new admin account and assigns the selected roles.",
    confirmSaveAccountEditDescription:
      "This updates account details for admin {{name}}, including status and password changes.",
    confirmSaveRolePermissionsTitle: "Save role permissions?",
    confirmSaveRolePermissionsDescription:
      "This updates feature permissions for role “{{name}}”. All admins bound to this role are affected immediately.",
    confirmSaveRoleTitle: "Save role details?",
    confirmSaveRoleCreateDescription: "This creates a new role {{name}}.",
    confirmSaveRoleEditDescription: "This updates the name, description, and status of role {{name}}.",
  },
  "agents.json": {
    "lineUi.profileReadOnlyHint":
      "Share, credit, and rebate are configured by your upline. Contact your parent agent or the platform to request changes.",
    "lineUi.selfAgentOverviewHint":
      "Credit below is allocated by your upline. Share and rebate are maintained upstream; this account cannot view or edit them.",
    "lineUi.overviewDownlineHint": "{{count}} direct downline agent(s). Manage them in the Downline tab.",
    "lineUi.playersUnavailableHint":
      "This agent cannot create players. Enable “Allow create player” in the agent profile first.",
    "lineUi.playersNoPermissionHint": "This account does not have player management permission on this node.",
    "lineUi.downlineColumns.email": "Email",
    "lineUi.downlineColumns.downlineCount": "Downline count",
    "lineUi.editCurrent": "Edit this agent",
    "lineUi.profileTabHint":
      "Maintain share, credit, rebate, and risk tags here. Use Account & status for login name and password.",
    "lineUi.nextSteps": "Suggested next steps",
    "lineUi.stepProfile": "Configure share, credit, and rebate",
    "lineUi.stepDownline": "Manage direct downline ({{count}} now)",
    "lineUi.stepPlayers": "Create or maintain direct players",
    "lineUi.downlineEmpty": "No direct downline yet. Child agents will appear here after creation.",
    "lineUi.downlineEmptyShort": "No direct downline yet.",
    "lineUi.sidebarShareRate": "Share {{rate}}%",
    "lineUi.sidebarAvailableCredit": "Available to grant {{amount}}",
    "lineUi.expand": "Expand",
    "lineUi.collapse": "Collapse",
    "profile.relativeShareRate": "Share rate (% of parent)",
    "profile.relativeShareRateValue": "{{rate}}% of parent",
    "profile.capabilityHint":
      "Takes effect on this agent’s primary account after save. If the platform Agent role included player/node management, login permissions are now tightened by these switches.",
    "profile.parentCaps": "Parent share {{share}}%, available to grant {{credit}}",
    "profile.availableCredit": "Available to grant {{amount}}",
    "profile.riskTags": "Risk tags",
    "profile.riskTagsPlaceholder": "Comma-separated, e.g. overdue, high_turnover",
    "profile.saveSuccess": "Share and credit settings saved",
    "settlementBills.platform": "Platform",
    "settlementBills.filteredByPeriod": "Showing bills for period #{{id}} only",
    "settlementBills.clearPeriodFilter": "Show all periods",
    "settlementBills.columns.party": "Party",
    "settlementBills.columns.counterparty": "Counterparty",
    "settlementBills.columns.grossWinLoss": "Win/Loss",
    "settlementBills.detail": "Details",
    "settlementBills.confirm": "Confirm bill",
    "settlementBills.confirmed": "Confirmed",
    "settlementBills.paymentAmount": "Payment amount",
    "settlementBills.recordPayment": "Record payment",
    "settlementBills.paid": "Payment recorded",
    "settlementBills.subtreeSummary": "Subtree summary",
    "settlementBills.grossWinLoss": "Win/Loss (gross_win_loss)",
    "settlementBills.rebateAmount": "Rebate",
    "settlementBills.shareProfit": "Share profit",
    "settlementBills.platformRounding": "Platform rounding",
    "settlementReports.title": "Period reports (credit share line)",
    "settlementReports.description":
      "Report set: player win/loss, agent share, rebate, credit, unpaid/overdue bills, and platform P/L.",
    "settlementReports.type": "Report type",
    "settlementReports.footnote":
      "These reports use credit-line period semantics, not legacy wallet commission/rebate reports.",
    "settlementReports.noPeriodHint":
      "When no specific period is selected, the last 7 days are used. Platform P/L requires a period.",
    "settlementReports.types.summary": "Summary",
    "settlementReports.types.player_win_loss": "Player win/loss",
    "settlementReports.types.agent_share": "Agent share",
    "settlementReports.types.rebate": "Rebate",
    "settlementReports.types.credit": "Credit",
    "settlementReports.types.unpaid_bills": "Unpaid bills",
    "settlementReports.types.overdue": "Overdue",
    "settlementReports.types.platform_pnl": "Platform P/L",
    "settlementReports.types.draw_period": "By draw",
    "settlementReports.summary.billCount": "Bill count",
    "settlementReports.summary.totalNet": "Total net",
    "settlementReports.summary.totalUnpaid": "Total unpaid",
    "settlementReports.summary.overdueCount": "Overdue bills",
    "settlementReports.summary.platformRounding": "Total platform rounding",
    "settlementReports.rebate.accrued": "Accrued",
    "settlementReports.rebate.inBill": "In bill",
    "settlementReports.rebate.settled": "Settled",
    "settlementReports.rebate.allocated": "Allocated",
    "settlementReports.credit.agents": "Agent credit",
    "settlementReports.credit.players": "Player credit",
    "settlementReports.platformPnl.periodRequired": "Select a specific period to view platform P/L.",
    "settlementReports.platformPnl.billNet": "Platform bill net",
    "settlementReports.platformPnl.rounding": "Rounding adjustment",
    "settlementReports.platformPnl.shareProfit": "Share profit (metadata)",
    "settlementReports.columns.player": "Player",
    "settlementReports.columns.gameType": "Play",
    "settlementReports.columns.grossWinLoss": "Win/Loss",
    "settlementReports.columns.rebate": "Rebate",
    "settlementReports.columns.agentId": "Agent ID",
    "settlementReports.columns.count": "Count",
    "settlementReports.columns.billId": "Bill",
    "settlementReports.columns.billType": "Type",
    "settlementReports.columns.unpaid": "Unpaid",
    "settlementReports.columns.status": "Status",
    "settlementReports.columns.overdueDays": "Overdue days",
    "settlementReports.columns.drawNo": "Draw no.",
    "settlementReports.columns.code": "Code",
    "settlementReports.columns.name": "Name",
    "settlementReports.columns.creditLimit": "Credit limit",
    "settlementReports.columns.allocated": "Allocated",
    "settlementReports.columns.available": "Available",
    "settlementReports.columns.used": "Used",
    "settlementReports.columns.frozen": "Frozen",
    "settlementReports.columns.rebateType": "Rebate type",
    "settlementReports.columns.amount": "Amount",
    "settlementPeriods.title": "Agent periods",
    "settlementPeriods.presetHint": "Quick period presets (recommended)",
    "settlementPeriods.openWithPreset": "Apply quick preset",
    "settlementPeriods.showAdvanced": "Custom start/end dates",
    "settlementPeriods.hideAdvanced": "Hide custom dates",
    "settlementPeriods.range": "Period",
    "settlementPeriods.empty": "No periods yet. Choose a quick preset and open a period.",
    "settlementPeriods.start": "Start",
    "settlementPeriods.end": "End",
    "settlementPeriods.status": "Status",
    "settlementPeriods.close": "Close period and generate bills",
    "settlementPeriods.viewBills": "Bills",
    "settlementPeriods.opened": "Period opened",
    "settlementPeriods.closed": "Period closed; bills generated",
    "settlementPeriods.closeFailed": "Failed to close period",
    "lineProvision.siteCode": "Integration site",
    "lineProvision.siteCodePlaceholder": "Select site",
    "lineProvision.siteRequired": "Select an integration site",
    "lineProvision.noUnboundSite": "No sites without a level-1 agent",
    "lineProvision.openIntegrationSites": "Go to integration sites",
    "lineProvision.passwordHint": "At least 6 characters",
    "playersPanel.siteCode": "Line site",
    "playersPanel.passwordHint": "At least 6 characters",
    "playersPanel.passwordMinLength": "Initial password must be at least 6 characters",
    "playersPanel.creditLimitInvalid": "Credit limit must be an integer ≥ 0",
    "playersPanel.creditLimitExceeded": "Credit limit cannot exceed this agent’s available grant",
    "playersPanel.rebateRateInvalid": "Rebate rate must be between 0 and 100%",
    "playersPanel.fundingMode": "Funding mode",
    "playersPanel.authSource": "Auth source",
    "playersPanel.rebateInherited": "Inherit agent default rebate",
    "playersPanel.creditListHint":
      "Credit line: below shows player credit limits and available credit, not main-site wallet balance.",
    "playersPanel.playerRef": "Player ref",
    "playersPanel.usernameNickname": "Username / nickname",
    "playersPanel.creditLimitAvailable": "Credit limit / available",
    "roles.selectedCount": "{{selected}} / {{total}} selected",
    "roles.groupSelectedCount": "{{selected}} / {{total}}",
    "roles.selectGroup": "Select all in group",
    "roles.noAssignablePermissions": "No permissions available to assign",
  },
  "config.json": {
    "integrationSites.columns.currency": "Currency",
    "integrationSites.columns.lineRoot": "Level-1 agent",
    "integrationSites.columns.h5Url": "Lottery H5",
    "integrationSites.columns.ssoSecret": "SSO secret",
    "integrationSites.columns.walletApiKey": "Wallet API key",
    "integrationSites.lineRootBound": "Bound",
    "integrationSites.lineRootUnbound": "Unbound",
    "integrationSites.secretNotConfigured": "Secret not configured",
    "integrationSites.secretCopyRequiresManage":
      "Integration site manage permission is required to copy secrets",
  },
  "jackpot.json": {
    confirmSavePoolTitle: "Save jackpot pool settings?",
    confirmSavePoolDescription:
      "This updates fill rate, thresholds, payout ratio, and related parameters (not pool balance). Use Balance adjustment for balance changes.",
  },
  "players.json": {
    rebateRate: "Rebate",
    riskTags: "Risk tags",
  },
};

function setNested(obj, dotKey, value) {
  const parts = dotKey.split(".");
  let cur = obj;
  for (let i = 0; i < parts.length - 1; i++) {
    if (!(parts[i] in cur) || typeof cur[parts[i]] !== "object" || Array.isArray(cur[parts[i]])) {
      cur[parts[i]] = {};
    }
    cur = cur[parts[i]];
  }
  cur[parts[parts.length - 1]] = value;
}

function sortKeysDeep(value) {
  if (Array.isArray(value)) return value;
  if (value && typeof value === "object") {
    return Object.fromEntries(
      Object.keys(value)
        .sort()
        .map((k) => [k, sortKeysDeep(value[k])]),
    );
  }
  return value;
}

let merged = 0;
for (const [file, keys] of Object.entries(translations)) {
  const enPath = path.join(localesDir, "en", file);
  const en = JSON.parse(fs.readFileSync(enPath, "utf8"));
  for (const [dotKey, enValue] of Object.entries(keys)) {
    setNested(en, dotKey, enValue);
    merged++;
  }
  fs.writeFileSync(enPath, `${JSON.stringify(sortKeysDeep(en), null, 2)}\n`, "utf8");
  console.log(`Updated ${file} (+${Object.keys(keys).length} keys)`);
}
console.log(`Total merged: ${merged}`);
