/** Propagation types for PixieSprout cuttings, seeds, and divisions. */

import type { CareSchedule } from './care';

export type RootingMethod =
  | 'water'
  | 'soil'
  | 'sphagnum_moss'
  | 'LECA'
  | 'perlite'
  | 'air'
  | 'vermiculite'
  | 'rockwool';

export type PropagationDifficulty = 'easy' | 'moderate' | 'challenging' | 'expert';

export type SuccessRate = 'high' | 'medium' | 'low';

export type PropagationStage =
  | 'cut_taken'
  | 'callusing'
  | 'rooting'
  | 'rooted'
  | 'potted_up'
  | 'established';

export interface PropagationMethod {
  method: RootingMethod;
  difficulty: PropagationDifficulty;
  instructions: string[];
  timeframe: string;
  successRate: SuccessRate;
  risks: string;
  warning: string | null;
}

export interface NotRecommendedMethod {
  method: RootingMethod;
  reason: string;
}

/**
 * Permissive snapshot shape used for older/raw scan data saved before method
 * normalization. Keep this separate from the canonical PropagationInfo model.
 */
export type SavedPropagationMethod = {
  method: string;
  difficulty: string;
  instructions?: string[];
  timeframe: string;
  successRate: string;
  risks?: string;
  warning?: string | null;
};

export type SavedPropagationInfo = {
  canPropagate: boolean;
  methods: SavedPropagationMethod[];
  notRecommended: Array<{ method: string; reason: string }>;
  generalNote?: string;
};

export interface PropagationInfo {
  canPropagate: boolean;
  methods: PropagationMethod[];
  notRecommended: NotRecommendedMethod[];
  generalNote?: string;
  /** AI-returned glossary of unique/DIY terms used in the guidance (Option B).
   *  Each entry lets the app render the term as tappable → popup. */
  glossary?: PropagationGlossaryEntry[];
}

/** A term the AI used in propagation guidance, with a definition + how-to. */
export interface PropagationGlossaryEntry {
  term: string;
  definition: string;
  howToMake?: string;
}

/** Curated fallback glossary (Option A) for common DIY propagation terms the
 *  AI may use even when it doesn't return a glossary. Keyed by lowercase term. */
export const FALLBACK_PROPAGATION_GLOSSARY: Record<string, PropagationGlossaryEntry> = {
  'willow water': {
    term: 'Willow water',
    definition: 'A homemade rooting aid made by steeping willow twigs in water. Willow bark contains salicylic acid and auxins that encourage root growth.',
    howToMake: 'Cut a few young willow twigs, chop them, and soak them in water for 24–48 hours. Use the strained water to soak your cuttings or water them in.',
  },
  'cinnamon': {
    term: 'Cinnamon',
    definition: 'A natural, mild antifungal that can help prevent rot on fresh cutting wounds. It is not a rooting hormone, but it protects the cut end.',
    howToMake: 'Dip the freshly cut end of your cutting into ground cinnamon before planting. Use plain cinnamon powder with no added sugar.',
  },
  'honey': {
    term: 'Honey',
    definition: 'A natural antibacterial that can help protect cutting wounds from infection and may gently support rooting.',
    howToMake: 'Dip the cut end of the cutting into a thin layer of raw honey, then plant it. Use raw, unprocessed honey for best results.',
  },
  'aloe gel': {
    term: 'Aloe gel',
    definition: 'The clear gel from an aloe vera leaf, used as a natural rooting aid and to keep cutting wounds moist and protected.',
    howToMake: 'Slice open a fresh aloe leaf, scoop out the clear gel, and coat the cut end of your cutting before planting.',
  },
  'rooting hormone': {
    term: 'Rooting hormone',
    definition: 'A powder, gel, or liquid containing auxins that stimulate root development on cuttings. Available in synthetic and organic forms.',
    howToMake: 'Available at garden centers. Dip the cut end into the powder or gel, tap off the excess, and plant immediately.',
  },
  'sphagnum moss': {
    term: 'Sphagnum moss',
    definition: 'A moisture-retentive, airy moss used as a rooting medium. It holds humidity around cuttings while letting roots breathe.',
    howToMake: 'Soak dried sphagnum moss in water, squeeze out excess, and wrap it around the cutting or fill a pot with it.',
  },
  'perlite': {
    term: 'Perlite',
    definition: 'A lightweight volcanic glass that improves drainage and aeration in rooting mixes, helping prevent rot.',
    howToMake: 'Mix perlite with potting soil or use it on its own as a free-draining rooting medium.',
  },
  'vermiculite': {
    term: 'Vermiculite',
    definition: 'A mineral that holds water and nutrients well, making it a good moisture-retentive rooting medium.',
    howToMake: 'Use vermiculite on its own or mixed with perlite/soil for cuttings that like consistent moisture.',
  },
  'air layering': {
    term: 'Air layering',
    definition: 'A propagation method where roots form on a stem while it is still attached to the parent plant, then the rooted section is cut off.',
    howToMake: 'Wound a stem, wrap it in moist sphagnum moss, cover with plastic, and wait for roots to form before severing.',
  },
  'willow': {
    term: 'Willow',
    definition: 'A fast-growing tree whose bark contains natural rooting compounds (auxins and salicylic acid) used to make willow water.',
    howToMake: 'Harvest young willow twigs in spring and steep them in water to make a natural rooting aid.',
  },
};

export interface PlantEnrichmentData {
  bio: string;
  wikiUrl?: string;
  lightNeeds: string;
  waterNeeds: string;
  soilNeeds: string;
  humidity: string;
  careDifficulty: 'easy' | 'medium' | 'fussy';
  commonIssues: string[];
  funFact: string;
  enrichedAt: string;
}

export interface PlantEnrichmentResult {
  enrichmentData: PlantEnrichmentData;
  propagationInfo: PropagationInfo;
  /** Structured AI care schedule (Class 1 scheduled + Class 3 observation). */
  careSchedule?: CareSchedule;
}

export const ROOTING_METHOD_ICONS: Record<RootingMethod, string> = {
  water: '💧',
  soil: '🪴',
  sphagnum_moss: '🌿',
  LECA: '🟤',
  perlite: '⚪',
  air: '💨',
  vermiculite: '🪨',
  rockwool: '🧱',
};

export const ROOTING_METHOD_LABELS: Record<RootingMethod, string> = {
  water: 'Water',
  soil: 'Soil',
  sphagnum_moss: 'Sphagnum Moss',
  LECA: 'LECA',
  perlite: 'Perlite',
  air: 'Air Layering',
  vermiculite: 'Vermiculite',
  rockwool: 'Rockwool',
};

export const DIFFICULTY_COLORS: Record<PropagationDifficulty, string> = {
  // WCAG AA ≥ 4.5:1 on cream (#FFFCF5) — darker variants of the brand palette.
  easy: '#3E7A33',
  moderate: '#8A5D20',
  challenging: '#B74A35',
  expert: '#8E3D2F',
};

export const SUCCESS_RATE_LABELS: Record<SuccessRate, string> = {
  high: 'High success rate',
  medium: 'Moderate success rate',
  low: 'Low success rate',
};

/**
 * Cutting-appropriate rooting methods. Seed propagation (germination) and
 * division/pup methods are deliberately NOT here — they belong to the
 * seed-packet and division flows, never the "Take a Cutting" flow.
 */
export const CUTTING_ROOTING_METHODS: readonly RootingMethod[] = [
  'water',
  'soil',
  'sphagnum_moss',
  'LECA',
  'perlite',
  'air',
  'vermiculite',
  'rockwool',
];

const CUTTING_METHOD_SET: ReadonlySet<string> = new Set(CUTTING_ROOTING_METHODS);

/** Sound canonical-value guard used internally before alias normalization. */
function isCanonicalCuttingMethod(method: string): method is RootingMethod {
  return CUTTING_METHOD_SET.has(method);
}

/**
 * AI sometimes returns the cutting TYPE ("softwood cutting", "stem cutting")
 * instead of a rooting MEDIUM (water, soil...). Map common synonyms to the
 * closest rooting medium so real cutting methods survive the filter.
 */
const CUTTING_SYNONYM_MAP: Readonly<Record<string, RootingMethod>> = {
  // Exact rooting media (lookup keys are normalized to lowercase words).
  water: 'water',
  soil: 'soil',
  sphagnum: 'sphagnum_moss',
  'sphagnum moss': 'sphagnum_moss',
  moss: 'sphagnum_moss',
  leca: 'LECA',
  'leca pellets': 'LECA',
  'clay pebbles': 'LECA',
  perlite: 'perlite',
  pumice: 'perlite',
  air: 'air',
  'air layering': 'air',
  vermiculite: 'vermiculite',
  rockwool: 'rockwool',
  'stone wool': 'rockwool',
  // Cutting types → sensible default medium.
  softwood: 'soil',
  'softwood cutting': 'soil',
  'soft wood': 'soil',
  hardwood: 'soil',
  'hardwood cutting': 'soil',
  'hard wood': 'soil',
  'semi hardwood': 'soil',
  'semi hardwood cutting': 'soil',
  'semi ripe': 'soil',
  'semi ripe cutting': 'soil',
  stem: 'water',
  'stem cutting': 'water',
  tip: 'water',
  'tip cutting': 'water',
  nodal: 'water',
  'nodal cutting': 'water',
  internodal: 'soil',
  leaf: 'perlite',
  'leaf cutting': 'perlite',
  'leaf petiole': 'perlite',
  root: 'soil',
  'root cutting': 'soil',
  heel: 'soil',
  'heel cutting': 'soil',
  basal: 'soil',
  'basal cutting': 'soil',
  cane: 'soil',
  'cane cutting': 'soil',
};

/** Normalize separators and whitespace before looking up an AI-provided method. */
function cuttingMethodLookupKey(method: string): string {
  return method
    .trim()
    .toLowerCase()
    .replace(/[_\-‐‑‒–—]+/g, ' ')
    .replace(/\s+/g, ' ');
}

/** Normalize an AI-returned method string to a real rooting medium, or null. */
export function normalizeCuttingMethod(method: string | undefined | null): RootingMethod | null {
  if (typeof method !== 'string') return null;
  const trimmed = method.trim();
  if (!trimmed) return null;
  if (isCanonicalCuttingMethod(trimmed)) return trimmed;
  return CUTTING_SYNONYM_MAP[cuttingMethodLookupKey(trimmed)] ?? null;
}

/**
 * True when a value is a canonical cutting method or a recognized AI synonym.
 * Canonical-typed callers retain a sound type guard; general strings deliberately
 * return boolean because a synonym such as "stem cutting" is not itself a
 * RootingMethod until normalizeCuttingMethod converts it.
 */
export function isCuttingMethod(
  method: RootingMethod | undefined | null,
): method is RootingMethod;
export function isCuttingMethod(method: string | undefined | null): boolean;
export function isCuttingMethod(method: string | undefined | null): boolean {
  return normalizeCuttingMethod(method) !== null;
}

/**
 * Safe label lookup — normalizes known synonyms first, then falls back to a
 * humanized raw string so unknown AI values never render as "undefined".
 */
export function cuttingMethodLabel(method: string | undefined | null): string {
  if (typeof method !== 'string') return 'Unknown method';
  const normalized = normalizeCuttingMethod(method);
  if (normalized) return ROOTING_METHOD_LABELS[normalized];
  const humanized = method
    .trim()
    .replace(/[_\-‐‑‒–—]+/g, ' ')
    .replace(/\s+/g, ' ');
  return humanized || 'Unknown method';
}

/**
 * Filters a propagation methods array down to cutting-appropriate methods,
 * normalizing cutting-type synonyms to their rooting medium so labels/icons work.
 */
export function filterCuttingMethods(methods: PropagationMethod[] | undefined | null): PropagationMethod[] {
  if (!Array.isArray(methods)) return [];
  return methods
    .map(method => {
      if (!method || typeof method.method !== 'string') return null;
      const normalized = normalizeCuttingMethod(method.method);
      if (!normalized) return null;
      return normalized === method.method ? method : { ...method, method: normalized };
    })
    .filter((method): method is PropagationMethod => method !== null);
}

/**
 * Function words that signal a sentence was cut off mid-phrase. When an
 * instruction ends on one of these, the AI almost certainly truncated it
 * (e.g. "...remove a ring of" — ends on "of"). A complete thought ends on a
 * content word or punctuation, never on a dangling preposition/conjunction.
 */
const TRUNCATION_END_WORDS: ReadonlySet<string> = new Set([
  // Conservative list: omit words that can validly end an imperative phrase
  // (for example, "pot it up", "water from below", or "remove them all").
  'of', 'to', 'in', 'on', 'at', 'by', 'for', 'with', 'from', 'into', 'onto',
  'upon', 'during', 'between', 'under', 'about', 'against', 'without', 'within',
  'along', 'beside', 'beyond', 'except', 'since', 'throughout', 'toward', 'via',
  // conjunctions / clause introducers
  'and', 'or', 'but', 'so', 'because', 'although', 'while', 'whereas', 'unless',
  'if', 'than', 'which', 'when', 'where', 'until',
  // articles
  'a', 'an', 'the',
  // auxiliaries that almost always need a following word when unpunctuated
  'is', 'are', 'was', 'were', 'been', 'being', 'does', 'did', 'has', 'had',
  'will', 'would', 'shall', 'should', 'can', 'could', 'may', 'might', 'must',
  'not', 'no', 'nor',
]);

/**
 * True when an instruction string looks truncated — it ends on a dangling
 * function word (preposition/conjunction/article/auxiliary), dangling dash,
 * or clause punctuation. A complete thought ends on a content word or terminal
 * sentence punctuation.
 */
export function isTruncatedInstruction(step: string | undefined | null): boolean {
  if (typeof step !== 'string') return true;
  const trimmed = step.trim();
  if (!trimmed) return true;

  // Ignore closing quotes/brackets while classifying the actual sentence ending.
  const terminalCore = trimmed.replace(/["'’”»)\]}]+$/, '');
  // A dangling dash or clause punctuation is a strong mid-phrase signal.
  if (/[-–—,:;]$/.test(terminalCore)) return true;
  // An ellipsis normally signals an unfinished thought rather than a complete step.
  if (/(?:\.{3}|…)$/.test(terminalCore)) return true;
  // Terminal sentence punctuation wins over the word heuristic. This avoids
  // incorrectly dropping complete phrases such as "Do not." or "Use all.".
  if (/[.!?]$/.test(terminalCore)) return false;

  const lastWord = terminalCore
    .split(/\s+/)
    .pop()
    ?.toLowerCase()
    .replace(/[^a-z]+$/g, '') ?? '';
  return lastWord.length > 0 && TRUNCATION_END_WORDS.has(lastWord);
}

/**
 * Removes truncated instruction steps from a method's instructions array.
 * Returns the cleaned array (may be empty if all steps were truncated).
 */
export function cleanInstructions(instructions: string[] | undefined | null): string[] {
  if (!Array.isArray(instructions)) return [];
  return instructions.reduce<string[]>((cleaned, step) => {
    if (typeof step !== 'string') return cleaned;
    const trimmed = step.trim();
    if (!isTruncatedInstruction(trimmed)) cleaned.push(trimmed);
    return cleaned;
  }, []);
}

/**
 * True when a timeframe string is a SHORT value — just a duration like
 * "4-6 weeks", "8-12 weeks", "2-4 months" — with no sentence around it.
 * Used by the cutting modal to decide layout: a short value sits INLINE next
 * to the "Timeframe" label; a full sentence drops BELOW it (Bekky, 2026-08-20).
 */
export function isShortTimeframe(timeframe: string | undefined | null): boolean {
  if (typeof timeframe !== 'string') return false;
  const t = timeframe.trim();
  if (t.length === 0) return false;
  // A bare duration: number(s) + unit (weeks/days/months), optionally with a
  // range or "approx"/"about" prefix. No sentence, no extra clause.
  return /^(?:(?:about|approx(?:imately)?\.?)\s+|~\s*)?\d+(?:\s*(?:[-–—]|to)\s*\d+)?\s*(?:weeks?|days?|months?|years?)\.?$/i.test(t);
}

/**
 * Normalize an AI-returned success-rate string to the canonical enum.
 * The AI sometimes writes "moderate" (not "medium"), "high success rate",
 * "good", "very high", etc. Map the common variants so the label map always
 * matches and the modal never shows a raw/inconsistent value.
 */
export function normalizeSuccessRate(rate: string | undefined | null): SuccessRate | null {
  if (typeof rate !== 'string') return null;
  const normalized = rate
    .trim()
    .toLowerCase()
    .replace(/[_\-‐‑‒–—]+/g, ' ')
    .replace(/\s+/g, ' ');
  if (!normalized) return null;

  if (/\b(?:high|excellent|great|good)\b/.test(normalized)) return 'high';
  if (/\b(?:medium|moderate|average|fair|decent)\b/.test(normalized)) return 'medium';
  if (/\b(?:low|poor|hard|difficult|unreliable)\b/.test(normalized)) return 'low';
  return null;
}

/**
 * Clean a propagation object's free-text fields that the AI can clip mid-phrase
 * (risks, warning) — the same truncation guard already applied to instructions.
 * A dangling sentence is dropped so the modal never shows a cut-off warning.
 */
export function cleanPropagationFreeText(
  propagation: PropagationInfo | null | undefined,
): PropagationInfo | null | undefined {
  if (!propagation) return propagation;
  const methods = Array.isArray(propagation.methods) ? propagation.methods : [];
  return {
    ...propagation,
    methods: methods.reduce<PropagationMethod[]>((cleaned, method) => {
      if (!method || typeof method !== 'object') return cleaned;

      const risks = typeof method.risks === 'string'
        && !isTruncatedInstruction(method.risks)
        ? method.risks.trim()
        : '';
      const warning = typeof method.warning === 'string'
        && !isTruncatedInstruction(method.warning)
        ? method.warning.trim()
        : null;

      cleaned.push({
        ...method,
        instructions: cleanInstructions(method.instructions),
        risks,
        warning,
      });
      return cleaned;
    }, []),
  };
}
