const { useState, useEffect, useRef, useMemo, createContext, useContext } = React;
const IFR_ROUTES = JSON.parse(document.getElementById('ifr-data').textContent);
const AIRPORTS = JSON.parse(document.getElementById('airports-data').textContent);
const FL_OPTIONS = ['F080','F090','F100','F110','F120','F130','F140','F150','F160','F170','F180'];
const WAKE_CAT = [{v:'L',label:'Light'},{v:'M',label:'Medium'},{v:'H',label:'Heavy'}];
const RULES_FULL = {I:'IFR',V:'VFR',Y:'IFR→VFR',Z:'VFR→IFR'};

const SEED_PLANS = [
  {id:'pl_001',name:'Ibiza — sábados',favorite:true,lastUsed:'2026-04-19',from:'LEMU',to:'LEIB',alt1:'LEVC',alt2:'LEMT',rules:'Z',type:'S',route:'RUXET IFR',fl:'F100',dur:{h:'01',m:'30'},obt:{h:'07',m:'00'},aircraft:'C172',reg:'ECOBE',wake:'L',equipment:'SGY',ssr:'C',colour:'BLANCA',operator:'EUROPEANFLYER',pic:'JESUS SOLER ROMERO',remarks:'VUELO DE ESCUELA',pob:'002',endurance:{h:'05',m:'00'},radio:'E',survival:'',jackets:'F',dinghies:'',tlf:'679165883'},
  {id:'pl_002',name:'Castellón IFR',favorite:true,lastUsed:'2026-04-17',from:'LEMU',to:'LECH',alt1:'LEVC',alt2:'LEMT',rules:'Z',type:'S',route:'VLC IFR M985 SOPET',fl:'F100',dur:{h:'01',m:'30'},obt:{h:'08',m:'30'},aircraft:'C172',reg:'ECOBE',wake:'L',equipment:'SGY',ssr:'C',colour:'BLANCA',operator:'EUROPEANFLYER',pic:'JESUS SOLER ROMERO',remarks:'VUELO DE ESCUELA',pob:'002',endurance:{h:'05',m:'00'},radio:'E',survival:'',jackets:'F',dinghies:'',tlf:'679165883'},
  {id:'pl_003',name:'Granada sur',favorite:false,lastUsed:'2026-04-10',from:'LEMU',to:'LEGR',alt1:'LEAM',alt2:'LEMT',rules:'Z',type:'S',route:'ASTRO IFR M985 VIBAS',fl:'F100',dur:{h:'02',m:'30'},obt:{h:'09',m:'00'},aircraft:'C172',reg:'ECOBE',wake:'L',equipment:'SGY',ssr:'C',colour:'BLANCA',operator:'EUROPEANFLYER',pic:'JESUS SOLER ROMERO',remarks:'VUELO DE ESCUELA',pob:'002',endurance:{h:'06',m:'00'},radio:'E',survival:'',jackets:'F',dinghies:'',tlf:'679165883'},
  {id:'pl_004',name:'Nav. local LEMU',favorite:false,lastUsed:'2026-04-05',from:'LEMU',to:'LEMU',alt1:'LEVC',alt2:'LEMT',rules:'V',type:'S',route:'DCT',fl:'F050',dur:{h:'01',m:'00'},obt:{h:'10',m:'00'},aircraft:'C172',reg:'ECOBE',wake:'L',equipment:'SGY',ssr:'C',colour:'BLANCA',operator:'EUROPEANFLYER',pic:'JESUS SOLER ROMERO',remarks:'VUELO LOCAL',pob:'002',endurance:{h:'04',m:'00'},radio:'E',survival:'',jackets:'F',dinghies:'',tlf:'679165883'}
];

// Build 60 days of history with weekly patterns so stats are meaningful
function buildSeedHistory(){
  const base = [
    {plan:'pl_001',name:'Ibiza — sábados',from:'LEMU',to:'LEIB',rules:'Z',fl:'F100',route:'RUXET IFR',dur:'01:30',durMin:90},
    {plan:'pl_002',name:'Castellón IFR',from:'LEMU',to:'LECH',rules:'Z',fl:'F100',route:'VLC IFR M985 SOPET',dur:'01:30',durMin:90},
    {plan:'pl_003',name:'Granada sur',from:'LEMU',to:'LEGR',rules:'Z',fl:'F100',route:'ASTRO IFR M985 VIBAS',dur:'02:30',durMin:150},
    {plan:'pl_004',name:'Nav. local LEMU',from:'LEMU',to:'LEMU',rules:'V',fl:'F050',route:'DCT',dur:'01:00',durMin:60}
  ];
  const out = [];
  const today = new Date('2026-04-20T10:00:00');
  // pattern: Wed/Sat/Sun are flight days, with higher Sat
  for (let d=0; d<65; d++){
    const date = new Date(today); date.setDate(today.getDate()-d);
    const dow = date.getDay(); // 0=Sun..6=Sat
    let n = 0;
    if (dow===6) n = Math.random()<0.85 ? 2 : 1;
    else if (dow===0) n = Math.random()<0.7 ? 1 : 0;
    else if (dow===3) n = Math.random()<0.6 ? 1 : 0;
    else if (dow===5) n = Math.random()<0.4 ? 1 : 0;
    for (let k=0; k<n; k++){
      const b = base[Math.floor(Math.random()*base.length)];
      const hour = 7 + Math.floor(Math.random()*4);
      const min = [0,15,30,45][Math.floor(Math.random()*4)];
      date.setHours(hour, min, 0, 0);
      const ok = Math.random()>0.06;
      out.push({
        id:`h_${d}_${k}`, planId:b.plan, planName:b.name,
        from:b.from, to:b.to, when:date.toISOString(),
        status: ok?'ok':'error', fpId: ok?`FYS${20+Math.floor(Math.random()*80)}${String.fromCharCode(65+Math.floor(Math.random()*26))}${String.fromCharCode(65+Math.floor(Math.random()*26))}`:null,
        error: ok?null:'Timeout ENAIRE',
        dur:b.dur, durMin:b.durMin, rules:b.rules, fl:b.fl,
        aircraft:'C172', reg:'ECOBE', route:b.route, pob:'002', pic:'JESUS SOLER ROMERO'
      });
    }
  }
  return out.sort((a,b)=>b.when.localeCompare(a.when));
}
const SEED_HISTORY = buildSeedHistory();

// Flota European Flyers — extraído de la tabla de pesos 2026
// European Flyers fleet — actualizado 06/05/2024
const EF_FLEET = [
  // C-172 R/S (Gasolina TAE125)
  {reg:'EC-NVG',type:'C172S',ssr:'C',com:'SFGY',wake:'L',weight:1675,arm:42,moment:70.35,letra:'M'},
  {reg:'EC-KOG',type:'C172R',ssr:'C',com:'SDFGY',wake:'L',weight:1722.5,arm:40.72,moment:70.15,letra:'E'},
  {reg:'EC-KPO',type:'C172R',ssr:'C',com:'SFGY',wake:'L',weight:1731.5,arm:40.75,moment:70.56,letra:'F'},
  {reg:'EC-KLA',type:'C172R',ssr:'C',com:'SDGY',wake:'L',weight:1748.9,arm:41.3,moment:72.18,letra:'F'},
  {reg:'EC-KVX',type:'C172S',ssr:'C',com:'SFGY',wake:'L',weight:1761.2,arm:41.57,moment:73.21,letra:'N'},
  {reg:'EC-KBL',type:'C172S',ssr:'C',com:'SDGY',wake:'L',weight:1757.1,arm:41.66,moment:73.204,letra:'G'},
  {reg:'EC-LJD',type:'C172S',ssr:'C',com:'SFGY',wake:'L',weight:1728.4,arm:42.78,moment:71.41,letra:'H'},
  // C-172 Diesel
  {reg:'EC-ITX',type:'C172R',ssr:'C',com:'SDFY',wake:'L',weight:1778.64,arm:41.15,moment:73.18,letra:'A',diesel:true},
  {reg:'EC-KNI',type:'C172R',ssr:'C',com:'SDFGY',wake:'L',weight:1825,arm:41.08,moment:74.96,letra:'D',diesel:true},
  {reg:'EC-MOB',type:'C172S',ssr:'C',com:'SGY',wake:'L',weight:1848.5,arm:43.81,moment:80.97,letra:'I',diesel:true},
  {reg:'EC-NAK',type:'C172R',ssr:'C',com:'SDFGY',wake:'L',weight:1763,arm:41.31,moment:72.83,letra:'J',diesel:true},
  {reg:'EC-NAL',type:'C172S',ssr:'C',com:'SDFGY',wake:'L',weight:1782,arm:40.58,moment:72.31,letra:'K',diesel:true},
  {reg:'EC-NMG',type:'C172S',ssr:'C',com:'SGY',wake:'L',weight:1787.01,arm:44.43,moment:78.509,letra:'L',diesel:true},
  {reg:'EC-KNC',type:'C172R',ssr:'C',com:'SGY',wake:'L',weight:1813,arm:40.35,moment:73.15,letra:'C',diesel:true},
  // DA-42 NG
  {reg:'EC-MND',type:'DA42',ssr:'C',com:'SDFGY',wake:'L',weight:3292,arm:96.1,moment:316.3,letra:null},
  {reg:'EC-MNC',type:'DA42',ssr:'C',com:'SDFGY',wake:'L',weight:3286.5,arm:96.1,moment:315.7,letra:null},
  {reg:'EC-IIY',type:'A34',ssr:'C',com:'SD',wake:'L',weight:3084,arm:83.9,moment:258,letra:null},
  // Tecnam P-Mentor
  {reg:'EC-OBE',type:'P2008',ssr:'C',com:'SGY',wake:'L',weight:996.60,arm:68.50,moment:68.27,letra:'P'},
  {reg:'EC-OGQ',type:'P2008',ssr:'C',com:'SGY',wake:'L',weight:1001.07,arm:67.91,moment:67.98,letra:'Q'},
];

// Demo fallback (only used when backend unavailable)
const USERS_DB = {
  'demo@aeroplan.es':{name:'Piloto Demo',email:'demo@aeroplan.es',pass:'demo1234',enaireUser:'',enairePassSet:false,plan:'free',monthlyUsed:0,monthlyLimit:3,memberSince:'2026-01-01',nextRenewal:'—',isAdmin:false}
};

const SEED_ADMIN = {mrr:2480,mrrLastMonth:1960,activeSubs:62,totalUsers:89,signupsMonth:14,submissionsMonth:1287,revenueSpark:[620,680,720,820,940,1080,1280,1520,1780,2040,2260,2480],usersSpark:[20,28,34,42,48,54,58,64,70,76,82,89],submissionsSpark:[120,180,240,320,410,520,640,780,890,1010,1170,1287]};
const SEED_USERS = [
  {id:'u1',name:'Jesús Soler Romero',email:'jesus.soler@europeanflyers.com',plan:'owner',status:'active',since:'2026-03-01',sends:142,lastActive:'hace 2 min'},
  {id:'u9',name:'Tucho',email:'tucho',plan:'free-forever',status:'active',since:'2026-04-01',sends:5,lastActive:'ahora',note:'Pruebas'},
  {id:'u2',name:'Alberto Alonso',email:'alberto.alonso@ef.com',plan:'premium',status:'active',since:'2026-03-12',sends:87,lastActive:'hace 14 min'},
  {id:'u3',name:'María Pérez',email:'maria.p@ef.com',plan:'premium',status:'active',since:'2026-03-14',sends:62,lastActive:'hace 1h'},
  {id:'u4',name:'Carlos Ruiz',email:'carlos.r@gmail.com',plan:'free',status:'active',since:'2026-04-02',sends:3,lastActive:'hace 3h'},
  {id:'u5',name:'Laura Vega',email:'laura.vega@ef.com',plan:'premium',status:'active',since:'2026-03-20',sends:45,lastActive:'hace 5h'},
  {id:'u6',name:'Diego Torres',email:'d.torres@ef.com',plan:'free-forever',status:'active',since:'2026-03-30',sends:28,lastActive:'hace 1 día',note:'Amigo'},
  {id:'u7',name:'Sara Molina',email:'sara.m@outlook.com',plan:'premium',status:'blocked',since:'2026-03-08',sends:0,lastActive:'hace 10 días'},
  {id:'u8',name:'Pablo Gómez',email:'pablo.g@ef.com',plan:'premium',status:'active',since:'2026-04-01',sends:19,lastActive:'hace 2 días'}
];
const SEED_LOGS = [
  {id:'l1',when:'14:32:11',user:'Alberto Alonso',action:'SUBMIT_FP',detail:'LEMU → LEIB · 200 OK · FYS24BE',level:'info'},
  {id:'l2',when:'14:28:47',user:'Jesús Soler',action:'LOGIN',detail:'IP 88.14.22.1 · Madrid',level:'info'},
  {id:'l3',when:'14:20:03',user:'Sara Molina',action:'LOGIN_BLOCKED',detail:'Cuenta bloqueada',level:'warn'},
  {id:'l4',when:'14:15:58',user:'María Pérez',action:'SUBMIT_FP',detail:'LEMU → LECH · 200 OK',level:'info'},
  {id:'l5',when:'14:02:11',user:'Laura Vega',action:'UPDATE_PROFILE',detail:'Cambio contraseña ENAIRE',level:'info'},
  {id:'l6',when:'13:48:22',user:'Carlos Ruiz',action:'PAYWALL_HIT',detail:'Free: 3/3 envíos',level:'warn'},
  {id:'l7',when:'13:40:09',user:'Tucho',action:'LOGIN',detail:'Cuenta de pruebas',level:'info'},
  {id:'l8',when:'13:25:33',user:'Pablo Gómez',action:'UNUSUAL_LOCATION',detail:'IP nueva',level:'warn'}
];

// Map a submission DB row + parsed request_json to full history object
function ap_mapSub(s, req) {
  const dia = req['dateOfFlight.dia']||'';
  const mes = req['dateOfFlight.mes']||'';
  const anio = req['dateOfFlight.anio']||'';
  const flightDate = dia && mes ? (dia.toString().padStart(2,'0')+'/'+mes.toString().padStart(2,'0')+(anio?'/'+anio:'')) : '';
  const eobt = (req['eobtTime.hora']||'') + ':' + (req['eobtTime.minutos']||'');
  const endH = req['fp19.endurance.hora']||'';
  const endM = req['fp19.endurance.minutos']||'';
  return {
    id: 'sub_' + s.id,
    planId: s.plan_id,
    planName: s.plan_name || (req['data.departureAerodrome']||'') + ' - ' + (req['data.destinationAerodrome']||''),
    from: s.adep || req['data.departureAerodrome'] || '?',
    to: s.ades || req['data.destinationAerodrome'] || '?',
    when: new Date(s.created_at * 1000).toISOString(),
    status: s.status === 'ok' ? 'ok' : 'error',
    fpId: s.icaro_ref || null,
    error: s.error_message || null,
    durationMs: s.duration_ms || 0,
    // Identificacion
    arcid: req['data.flightId'] || '',
    rules: req['data.flightRules'] || '',
    flightType: req['data.flightType'] || '',
    flightDate: flightDate,
    // Aeronave
    aircraft: req['data.aircraftName'] || '',
    wake: req['data.wakeTurbulenceCategory'] || '',
    reg: req['data.aircraftRegistrationMarkings'] || '',
    comEquipment: req['data.aidEquipment'] || '',
    ssr: req['ssrData'] || '',
    // Ruta
    eobt: eobt !== ':' ? eobt : '',
    speed: req['data.crusingSpeed'] || '',
    fl: req['data.crusingLevel'] || '',
    route: req['data.route'] || '',
    dur: (req['estimatedElapsedTime.hora']||'00') + ':' + (req['estimatedElapsedTime.minutos']||'00'),
    durMin: parseInt(req['estimatedElapsedTime.hora']||'0')*60 + parseInt(req['estimatedElapsedTime.minutos']||'0'),
    altn: req['data.alternateAerodrome1'] || '',
    altn2: req['data.alternateAerodrome2'] || '',
    // Suplementario
    endurance: endH ? (endH+':'+(endM||'00')) : '',
    pob: req['fp19.personsOnBoard'] || '',
    pic: req['fp19.pilot'] || '',
    colors: req['fp19.aircraftColourAndMarkings'] || '',
    emergencyRadio: req['fp19.radio'] || '',
    survivalEquipment: req['fp19.survivalEquipment'] || '',
    lifeJackets: req['fp19.lifeJackets'] || '',
    // Observaciones
    rmk: req['data.remarks'] || '',
    opr: req['data.operator'] || '',
  };
}

const AppCtx = createContext(null);
const AppProvider = ({children}) => {
  const [route, setRoute] = useState({screen:'landing', params:{}});
  const loadTweaks = () => { try { const s=localStorage.getItem('ap_tweaks'); if(s) return {...window.TWEAK_DEFAULTS,...JSON.parse(s),theme:'light'}; } catch(e){} return {...window.TWEAK_DEFAULTS,theme:'light'}; };
  const [tweaks, setTweaks] = useState(loadTweaks);
  const [user, setUser] = useState(null);
  const [plans, setPlans] = useState([]);
  const [history, setHistory] = useState([]);
  const [toasts, setToasts] = useState([]);
  const go = (screen, params={}) => {
    setRoute({screen, params});
    try { localStorage.setItem('ap_route', JSON.stringify({screen, params})); } catch(e){}
    window.scrollTo({top:0});
  };
  useEffect(()=>{
    try { const s=localStorage.getItem('ap_route'); if(s) setRoute(JSON.parse(s)); const u=localStorage.getItem('ap_user'); if(u){ const parsed=JSON.parse(u); if(parsed.monthlyLimit==null) parsed.monthlyLimit=Infinity; setUser(parsed);} } catch(e){}
  }, []);
  useEffect(()=>{
    document.body.setAttribute('data-theme', 'light'); // Always light
    document.documentElement.style.setProperty('--radius', (tweaks.radius||14)+'px');
    try { localStorage.setItem('ap_tweaks', JSON.stringify(tweaks)); } catch(e){}
  }, [tweaks]);
  const [backendOK, setBackendOK] = useState(false);
  useEffect(()=>{ (async()=>{ try { setBackendOK(!!(await window.isBackendReady())); } catch(e){ setBackendOK(false); } })(); }, []);
  // Handle ?verify=TOKEN
  useEffect(()=>{
    const params = new URLSearchParams(window.location.search);
    const vToken = params.get('verify');
    if (vToken && backendOK && window.api) {
      window.api.verifyEmail(vToken).then(()=>{
        setUser(u => u ? {...u, email_verified: true} : u);
        try { const s=JSON.parse(localStorage.getItem('ap_user')||'{}'); s.email_verified=true; localStorage.setItem('ap_user',JSON.stringify(s)); } catch(e){}
        toast(t('auth.verified_ok') || 'Email verificado correctamente');
      }).catch(()=>{
        toast(t('auth.verify_fail') || 'Enlace de verificación inválido o expirado');
      });
      window.history.replaceState({}, '', window.location.pathname);
    }
  }, [backendOK]);
  const hasBackend = () => backendOK && !!window.api;
  // Al recargar pagina, si ya hay sesion y backend, cargar planes reales
  useEffect(()=>{
    if (!backendOK || !window.api) return;
    (async()=>{
      try {
        const me = await window.api.me();
        if (me && me.user) {
          const d = await window.api.plans();
          setPlans(Array.isArray(d.plans) ? d.plans : []);
          try {
            const subs = await window.api.submissions();
            if (Array.isArray(subs.submissions)) {
              const hist = subs.submissions.map(s => {
                let req = {};
                try { req = JSON.parse(s.request_json || '{}'); } catch(e){}
                return ap_mapSub(s, req);
              });
              setHistory(hist);
            }
          } catch(e){}
        }
      } catch(e){
        // No hay sesion activa o backend caido — cargar seed si hay user en localStorage
        try { if (localStorage.getItem('ap_user')) { setPlans(SEED_PLANS); setHistory(SEED_HISTORY); } } catch(ex){}
      }
    })();
  }, [backendOK]);
  const login = async (email, pass) => {
    // Si hay backend PHP, usa el endpoint real. Si no, caída demo con USERS_DB.
    if (await window.isBackendReady()) {
      try {
        const r = await window.api.login({email, password: pass});
        const u = r.user || {};
        const authed = {
          ...u,
          name: u.name, email: u.email,
          enaireUser: u.icaro_user_masked || '',
          enairePassSet: !!u.has_icaro_credentials,
          plan: u.plan,
          monthlyUsed: 0, monthlyLimit: u.plan === 'free' ? 3 : Infinity,
          isAdmin: u.role === 'admin',
          memberSince: new Date((u.created_at||0)*1000).toLocaleDateString('es-ES'),
          nextRenewal: u.plan_until ? new Date(u.plan_until*1000).toLocaleDateString('es-ES') : '—'
        };
        setUser(authed);
        try { localStorage.setItem('ap_user', JSON.stringify(authed)); } catch(e){}
        // Cargar planes reales (siempre reemplaza, incluso si vacio)
        try { const d = await window.api.plans(); setPlans(Array.isArray(d.plans) ? d.plans : []); } catch(e){ setPlans([]); }
        // Cargar historial de submissions
        try {
          const subs = await window.api.submissions();
          if (Array.isArray(subs.submissions)) {
            const hist = subs.submissions.map(s => {
              let req = {};
              try { req = JSON.parse(s.request_json || '{}'); } catch(e){}
              return ap_mapSub(s, req);
            });
            setHistory(hist);
          }
        } catch(e){}
        go('dashboard');
        return {ok:true};
      } catch (err) {
        return {ok:false, error: err.error || 'login_failed'};
      }
    }
    // No demo fallback — backend is required
    return {ok:false, error:'server_unavailable'};
  };
  const logout = async () => {
    if (await window.isBackendReady()) { try { await window.api.logout(); } catch(e){} }
    setUser(null); try { localStorage.removeItem('ap_user'); } catch(e){} go('landing');
  };
  const toast = (msg) => { const id = Math.random().toString(36).slice(2); setToasts(t=>[...t,{id,msg}]); setTimeout(()=>setToasts(t=>t.filter(x=>x.id!==id)), 2600); };
  // Custom confirm modal (replaces browser confirm())
  const [confirmState, setConfirmState] = useState(null);
  const confirm = (message, {title, yes, no} = {}) => new Promise(resolve => {
    setConfirmState({message, title: title || '', yes: yes || 'Eliminar', no: no || 'Cancelar', resolve});
  });
  return <AppCtx.Provider value={{route,go,tweaks,setTweaks,user,setUser,login,logout,plans,setPlans,history,setHistory,toasts,toast,confirm,data:{IFR_ROUTES,AIRPORTS,FL_OPTIONS,WAKE_CAT,RULES_FULL,SEED_ADMIN,SEED_USERS,SEED_LOGS}}}>
    {children}
    {confirmState && <div className="confirm-overlay" onClick={e=>{if(e.target===e.currentTarget){confirmState.resolve(false);setConfirmState(null);}}} style={{position:'fixed',inset:0,background:'rgba(0,0,0,.45)',backdropFilter:'blur(4px)',zIndex:400,display:'flex',justifyContent:'center',alignItems:'center',padding:20}}>
      <div className="card confirm-modal slide-up" style={{width:'100%',maxWidth:360,padding:28,textAlign:'center'}}>
        {confirmState.title && <div style={{fontWeight:600,fontSize:15,marginBottom:8}}>{confirmState.title}</div>}
        <p style={{fontSize:14,lineHeight:1.6,color:'var(--ink-dim)',marginBottom:20}}>{confirmState.message}</p>
        <div style={{display:'flex',gap:10}}>
          <button className="btn btn-ghost" style={{flex:1}} onClick={()=>{confirmState.resolve(false);setConfirmState(null);}}>{confirmState.no}</button>
          <button className="btn" style={{flex:1,background:'var(--err)',color:'#fff',border:'none'}} onClick={()=>{confirmState.resolve(true);setConfirmState(null);}}>{confirmState.yes}</button>
        </div>
      </div>
    </div>}
  </AppCtx.Provider>;
};
const useApp = () => useContext(AppCtx);

const Toasts = () => {
  const {toasts} = useApp();
  return <div style={{position:'fixed',top:20,left:0,right:0,display:'flex',flexDirection:'column',alignItems:'center',gap:8,zIndex:300,pointerEvents:'none'}}>
    {toasts.map(t=><div key={t.id} className="toast slide-up">{t.msg}</div>)}
  </div>;
};

// Custom date picker matching app style
const DatePicker = ({value, onChange, style: wrapStyle}) => {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  const t = window.t || (k => k);

  // Parse current value (YYYY-MM-DD)
  const parsed = value ? new Date(value + 'T00:00:00') : new Date();
  const [viewYear, setViewYear] = useState(parsed.getFullYear());
  const [viewMonth, setViewMonth] = useState(parsed.getMonth());

  // Close on outside click
  useEffect(() => {
    if (!open) return;
    const handler = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', handler);
    return () => document.removeEventListener('mousedown', handler);
  }, [open]);

  // When value changes externally, sync view
  useEffect(() => {
    if (value) {
      const d = new Date(value + 'T00:00:00');
      setViewYear(d.getFullYear());
      setViewMonth(d.getMonth());
    }
  }, [value]);

  const DAYS_ES = ['Lu','Ma','Mi','Ju','Vi','Sa','Do'];
  const DAYS_EN = ['Mo','Tu','We','Th','Fr','Sa','Su'];
  const MONTHS_ES = ['Enero','Febrero','Marzo','Abril','Mayo','Junio','Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre'];
  const MONTHS_EN = ['January','February','March','April','May','June','July','August','September','October','November','December'];

  const lang = (window.TWEAK_DEFAULTS?.lang || 'es');
  const dayNames = lang === 'en' ? DAYS_EN : DAYS_ES;
  const monthNames = lang === 'en' ? MONTHS_EN : MONTHS_ES;

  // Build calendar grid
  const firstDay = new Date(viewYear, viewMonth, 1);
  let startDow = firstDay.getDay() - 1; // Mon=0
  if (startDow < 0) startDow = 6;
  const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate();
  const prevDays = new Date(viewYear, viewMonth, 0).getDate();
  const cells = [];
  for (let i = startDow - 1; i >= 0; i--) cells.push({d: prevDays - i, cur: false});
  for (let i = 1; i <= daysInMonth; i++) cells.push({d: i, cur: true});
  const rem = 42 - cells.length;
  for (let i = 1; i <= rem; i++) cells.push({d: i, cur: false});

  const today = new Date();
  const todayStr = today.toISOString().slice(0, 10);
  const selDay = value || '';

  const pick = (day) => {
    const m = String(viewMonth + 1).padStart(2, '0');
    const d = String(day).padStart(2, '0');
    onChange(`${viewYear}-${m}-${d}`);
    setOpen(false);
  };

  const prevMonth = () => { if (viewMonth === 0) { setViewMonth(11); setViewYear(y => y - 1); } else setViewMonth(m => m - 1); };
  const nextMonth = () => { if (viewMonth === 11) { setViewMonth(0); setViewYear(y => y + 1); } else setViewMonth(m => m + 1); };

  // Format display value
  const displayVal = value ? (() => {
    const d = new Date(value + 'T00:00:00');
    return `${d.getDate()} ${monthNames[d.getMonth()].slice(0, 3)} ${d.getFullYear()}`;
  })() : '—';

  return <div ref={ref} style={{position:'relative', ...wrapStyle}}>
    <div className="field mono" onClick={() => setOpen(!open)} style={{cursor:'pointer',display:'flex',alignItems:'center',justifyContent:'space-between',userSelect:'none'}}>
      <span>{displayVal}</span>
      <Icon name="chevron-down" size={14} style={{color:'var(--ink-dim)',transform:open?'rotate(180deg)':'',transition:'transform .15s'}}/>
    </div>
    {open && <div style={{position:'absolute',top:'calc(100% + 6px)',left:0,zIndex:50,background:'var(--panel)',border:'1px solid var(--stroke)',borderRadius:'var(--radius)',boxShadow:'0 8px 32px rgba(0,0,0,.12)',padding:16,width:280,animation:'fadeSlideUp .15s ease'}}>
      {/* Month nav */}
      <div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:12}}>
        <button type="button" className="btn btn-ghost btn-icon" onClick={prevMonth} style={{padding:4}}><Icon name="arrow_right" size={14} style={{transform:'rotate(180deg)'}}/></button>
        <span style={{fontWeight:600,fontSize:14}}>{monthNames[viewMonth]} {viewYear}</span>
        <button type="button" className="btn btn-ghost btn-icon" onClick={nextMonth} style={{padding:4}}><Icon name="arrow_right" size={14}/></button>
      </div>
      {/* Day headers */}
      <div style={{display:'grid',gridTemplateColumns:'repeat(7,1fr)',gap:2,textAlign:'center',marginBottom:6}}>
        {dayNames.map(d => <div key={d} style={{fontSize:10,fontWeight:600,color:'var(--ink-low)',padding:'4px 0'}}>{d}</div>)}
      </div>
      {/* Days grid */}
      <div style={{display:'grid',gridTemplateColumns:'repeat(7,1fr)',gap:2}}>
        {cells.map((c, i) => {
          const dateStr = c.cur ? `${viewYear}-${String(viewMonth+1).padStart(2,'0')}-${String(c.d).padStart(2,'0')}` : '';
          const isToday = dateStr === todayStr;
          const isSel = dateStr === selDay;
          return <button type="button" key={i} onClick={() => c.cur && pick(c.d)} style={{
            width:34, height:34, borderRadius:10, border:'none', cursor: c.cur ? 'pointer' : 'default',
            background: isSel ? 'var(--accent)' : isToday ? 'var(--accent-soft)' : 'transparent',
            color: isSel ? '#fff' : !c.cur ? 'var(--ink-low)' : isToday ? 'var(--accent)' : 'var(--ink)',
            fontWeight: isToday || isSel ? 700 : 400,
            fontSize: 13, fontFamily: 'var(--mono)',
            transition: 'background .1s',
            opacity: c.cur ? 1 : .35,
          }}
          onMouseEnter={e => { if (c.cur && !isSel) e.target.style.background = 'var(--bg-inset)'; }}
          onMouseLeave={e => { if (c.cur && !isSel) e.target.style.background = isToday ? 'var(--accent-soft)' : 'transparent'; }}
          >{c.d}</button>;
        })}
      </div>
      {/* Today shortcut */}
      <div style={{marginTop:10,textAlign:'center'}}>
        <button type="button" className="btn btn-ghost btn-sm" style={{fontSize:11}} onClick={() => { pick(today.getDate()); setViewYear(today.getFullYear()); setViewMonth(today.getMonth()); }}>Hoy</button>
      </div>
    </div>}
  </div>;
};

const LogoMark = ({size=24}) => (
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" style={{flexShrink:0}}>
    <defs>
      <linearGradient id="apLogoGrad" x1="0" y1="0" x2="1" y2="1">
        <stop offset="0%" stopColor="#F4A261"/>
        <stop offset="50%" stopColor="#E76F51"/>
        <stop offset="100%" stopColor="#8B3A62"/>
      </linearGradient>
    </defs>
    <path d="M2.5 14.5 L21 4 L17.5 13.5 L11 15 L9.5 21 L7.5 19 L8 16 L5 15.5 L2.5 14.5 Z" fill="url(#apLogoGrad)" stroke="url(#apLogoGrad)" strokeWidth="1.3" strokeLinejoin="round"/>
  </svg>
);

const NAV_ITEMS = [
  {id:'dashboard',label:'nav.plans',icon:'list'},
  {id:'history',label:'nav.history',icon:'clock'},
  {id:'ifr-table',label:'nav.ifr',icon:'map'},
  {id:'profile',label:'nav.profile',icon:'user'}
];

const TopNav = () => {
  const t = window.t || (k => k);
  const {route, go, user} = useApp();
  if (!user) return null;
  const items = user.isAdmin ? NAV_ITEMS.concat([{id:'admin',label:'nav.admin',icon:'shield'}]) : NAV_ITEMS;
  return <nav className="topnav">
    <div className="topnav-inner">
      <div className="topnav-brand"><LogoMark/> AeroPlan</div>
      <div className="topnav-links">
        {items.map(it=>{
          const active = route.screen===it.id || (it.id==='dashboard' && (route.screen==='plan-editor'||route.screen==='send-flow'));
          return <button key={it.id} className={`topnav-link ${active?'active':''}`} onClick={()=>go(it.id)}>{t(it.label)}</button>;
        })}
      </div>
      <div className="topnav-right">
        <div className="chip">{(user.monthlyLimit===Infinity||user.monthlyLimit==null)?t('common.unlimited'):`${user.monthlyUsed||0}/${user.monthlyLimit}`}</div>
      </div>
    </div>
  </nav>;
};

const MobileTabBar = () => {
  const t = window.t || (k => k);
  const {route, go, user} = useApp();
  if (!user) return null;
  const items = (user.isAdmin ? NAV_ITEMS.concat([{id:'admin',label:'nav.admin',icon:'shield'}]) : NAV_ITEMS).slice(0,5);
  return <nav className="tab-bar">
    {items.map(it=>{
      const active = route.screen===it.id || (it.id==='dashboard' && (route.screen==='plan-editor'||route.screen==='send-flow'));
      return <button key={it.id} className={`tab-item ${active?'active':''}`} onClick={()=>go(it.id)}>
        <Icon name={it.icon} size={20} stroke={active?2:1.7}/>
        <span>{t(it.label)}</span>
      </button>;
    })}
  </nav>;
};

Object.assign(window,{useState,useEffect,useRef,useMemo,AppCtx,AppProvider,useApp,Toasts,TopNav,MobileTabBar,LogoMark,IFR_ROUTES,AIRPORTS,FL_OPTIONS,WAKE_CAT,RULES_FULL,EF_FLEET,SEED_PLANS,SEED_HISTORY,SEED_ADMIN,SEED_USERS,SEED_LOGS,USERS_DB});
