/**
 * Turn long Frappe/Python/MySQL error strings into short text safe for toasts.
 * For user-facing duplicate messages with i18n, use `extractDuplicateEntryId` in the UI
 * and translate there; `shortFrappeErrorMessage` is English fallbacks for other errors.
 */

const MAX_LEN = 220;

/** Extract MySQL duplicate key value from common Frappe error shapes */
export function extractDuplicateEntryId(raw: string): string | undefined {
  const m =
    raw.match(/Duplicate entry\s+['"]([^'"]+)['"]\s+for key/i) ||
    raw.match(/Duplicate entry\s+([^\s,')]+)\s+for key/i);
  return m?.[1]?.trim() || undefined;
}

function stripTracebackAndPickTail(raw: string): string {
  let s = raw.replace(/\r\n/g, '\n').trim();
  if (s.startsWith('["') && s.endsWith('"]')) {
    try {
      const parsed = JSON.parse(s) as unknown;
      if (Array.isArray(parsed) && typeof parsed[0] === 'string') {
        s = parsed[0];
      }
    } catch {
      /* keep s */
    }
  }
  if (s.includes('|')) {
    const parts = s.split('|');
    const tail = parts[parts.length - 1]?.trim() ?? s;
    if (tail.length > 0 && !tail.includes('File "')) {
      s = tail;
    }
  }
  if (s.includes('Traceback (most recent call last)')) {
    const idx = s.lastIndexOf('IntegrityError');
    if (idx >= 0) s = s.slice(idx);
    else {
      const lastLine = s
        .split('\n')
        .map((l) => l.trim())
        .filter((l) => l.length > 0 && !l.startsWith('File "'))
        .pop();
      if (lastLine && lastLine.length < 400) s = lastLine;
    }
  }
  return s.replace(/\s+/g, ' ').trim();
}

/**
 * Turn Frappe HTML validation messages into plain text for toasts and alerts.
 */
export function frappeHtmlToPlainText(html: string): string {
  if (!html || typeof html !== 'string') return '';
  let s = html
    .replace(/<br\s*\/?>/gi, '\n')
    .replace(/<\/(p|div|li|tr)>/gi, '\n')
    // Links may contain nested <strong> etc.; keep visible text only
    .replace(/<a[^>]*>([\s\S]*?)<\/a>/gi, (_, inner: string) =>
      inner.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim(),
    )
    .replace(/<[^>]+>/g, ' ');
  s = s
    .replace(/&nbsp;/g, ' ')
    .replace(/&amp;/g, '&')
    .replace(/&lt;/g, '<')
    .replace(/&gt;/g, '>')
    .replace(/&#39;/g, "'")
    .replace(/&quot;/g, '"');
  return s.replace(/[ \t]+\n/g, '\n').replace(/\n{3,}/g, '\n\n').replace(/  +/g, ' ').trim();
}

/** Parse Frappe API error body / thrown Error into a user-readable string (no raw HTML). */
export function formatFrappeApiError(err: unknown): string {
  if (err == null) return 'Something went wrong';
  if (err instanceof Error) return frappeHtmlToPlainText(err.message) || err.message;
  if (typeof err === 'string') return frappeHtmlToPlainText(err);
  const e = err as Record<string, unknown>;
  let msg = typeof e.message === 'string' ? e.message : '';
  if (e._server_messages) {
    try {
      const raw = e._server_messages;
      const arr = typeof raw === 'string' ? JSON.parse(raw) : raw;
      if (Array.isArray(arr)) {
        const parts: string[] = [];
        for (const item of arr) {
          try {
            const o = typeof item === 'string' ? JSON.parse(item) : item;
            if (o && typeof (o as { message?: string }).message === 'string') {
              parts.push(frappeHtmlToPlainText((o as { message: string }).message));
            } else if (typeof item === 'string') parts.push(frappeHtmlToPlainText(item));
          } catch {
            if (typeof item === 'string') parts.push(frappeHtmlToPlainText(item));
          }
        }
        if (parts.length) msg = parts.join('\n');
      }
    } catch {
      /* ignore */
    }
  }
  if (!msg && typeof e.exc === 'string' && e.exc.length < 800) msg = e.exc;
  if (!msg) return 'Request failed. Check stock, warehouses, and linked documents.';
  return frappeHtmlToPlainText(msg);
}


/**
 * Short English message for non-duplicate server errors (tracebacks stripped).
 */
export function shortFrappeErrorMessage(raw: string): string {
  if (!raw || typeof raw !== 'string') {
    return 'Something went wrong. Please try again.';
  }

  const dupId = extractDuplicateEntryId(raw);
  if (dupId || (/1062/i.test(raw) && /Duplicate entry/i.test(raw))) {
    return dupId
      ? `This ID is already in use (${dupId}). Use a different location name or ID.`
      : 'This record already exists. Use a unique name or ID.';
  }

  let s = stripTracebackAndPickTail(raw);

  const quoted = s.match(/IntegrityError\([^)]*"([^"]{1,180})"/i);
  if (quoted?.[1] && !quoted[1].includes('Traceback')) {
    s = quoted[1];
  }

  s = s.replace(/^\(['"]?\w+['"]?,\s*['"][^'"]*['"]?\s*,\s*/i, '');

  if (s.length > MAX_LEN) {
    return `${s.slice(0, MAX_LEN - 1)}…`;
  }
  return s || 'Something went wrong. Please try again.';
}

/** True when the raw error indicates a duplicate primary / unique key (MySQL 1062). */
export function isDuplicateKeyError(raw: string): boolean {
  return (
    !!extractDuplicateEntryId(raw) ||
    (/1062/i.test(raw) && /Duplicate entry/i.test(raw)) ||
    /IntegrityError\s*\(\s*1062/i.test(raw)
  );
}
