/* Admin — nómina, gastos, retiros y sincronización con entrenadores */

function notifyFinanceChanged (detail) {
  if (typeof notifyAcademyDataSync === 'function') {
    notifyAcademyDataSync('finance', detail);
  }
  window.dispatchEvent(new CustomEvent('tecos:finance-changed', { detail: detail || {} }));
}

const PAY_TYPE_LABELS = {
  hour: 'Por hora',
  day: 'Por día',
  week: 'Por semana',
  fixed: 'Monto fijo',
};

const WEEKDAY_LABELS = ['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb'];
const WEEKDAY_LABELS_FULL = ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'];

function emptyWeekSchedule () {
  return [0, 1, 2, 3, 4, 5, 6].map((weekday) => ({ weekday, hours: '', note: '' }));
}

function parseWeeklyHours (profile) {
  const raw = profile?.weekly_hours;
  const base = emptyWeekSchedule();
  if (!raw) return base;
  const list = Array.isArray(raw) ? raw : (typeof raw === 'string' ? (() => { try { return JSON.parse(raw); } catch { return []; } })() : []);
  list.forEach((entry) => {
    const wd = Number(entry?.weekday);
    if (wd >= 0 && wd <= 6) {
      base[wd] = {
        weekday: wd,
        hours: entry.hours != null && entry.hours !== '' ? String(entry.hours) : '',
        note: entry.note || '',
      };
    }
  });
  return base;
}

function normalizeWeeklyHoursForSave (schedule) {
  return (schedule || [])
    .map((e) => ({
      weekday: Number(e.weekday),
      hours: Number(e.hours) || 0,
      note: String(e.note || '').trim() || null,
    }))
    .filter((e) => e.weekday >= 0 && e.weekday <= 6 && e.hours > 0);
}

/** Calcula monto sugerido según tipo de pago y horas/días de la semana. */
function computePayrollWeekPreview (profile, schedule) {
  const rate = Number(profile?.rate_amount) || 0;
  const type = profile?.pay_type || 'hour';
  const entries = normalizeWeeklyHoursForSave(schedule);
  const totalHours = entries.reduce((s, e) => s + e.hours, 0);
  const daysWorked = entries.length;

  const needsRate = rate <= 0;

  if (type === 'hour') {
    const amount = Math.round(totalHours * rate * 100) / 100;
    return {
      label: totalHours > 0 ? `${totalHours} h × $${rate.toLocaleString('es-MX')} / h` : 'Por hora (sin horas capturadas)',
      amount,
      totalHours,
      daysWorked,
      needsRate,
      needsHours: totalHours <= 0,
      hint: needsRate
        ? 'Define la tarifa por hora en «Tarifa y tipo de pago».'
        : (totalHours <= 0 ? 'Captura horas en los días de la semana (Dom–Sáb).' : null),
    };
  }
  if (type === 'day') {
    const amount = Math.round(daysWorked * rate * 100) / 100;
    return {
      label: daysWorked > 0 ? `${daysWorked} día(s) × $${rate.toLocaleString('es-MX')} / día` : 'Por día (sin días con horas)',
      amount,
      totalHours,
      daysWorked,
      needsRate,
      needsHours: daysWorked <= 0,
      hint: needsRate
        ? 'Define la tarifa por día en «Tarifa y tipo de pago».'
        : (daysWorked <= 0 ? 'Pon horas en al menos un día para contarlo como día trabajado.' : null),
    };
  }
  if (type === 'week') {
    const amt = totalHours > 0 ? totalHours * rate : rate;
    return {
      label: totalHours > 0
        ? `${totalHours} h × $${rate.toLocaleString('es-MX')} / h`
        : `Tarifa semanal $${rate.toLocaleString('es-MX')}`,
      amount: Math.round(amt * 100) / 100,
      totalHours,
      daysWorked,
      needsRate,
      needsHours: false,
      hint: needsRate ? 'Define la tarifa semanal en «Tarifa y tipo de pago».' : null,
    };
  }
  return {
    label: `Monto fijo $${rate.toLocaleString('es-MX')}`,
    amount: rate,
    totalHours,
    daysWorked,
    needsRate,
    needsHours: false,
    hint: needsRate ? 'Define el monto fijo en «Tarifa y tipo de pago».' : null,
  };
}

function mondayOfWeek (d = new Date()) {
  const x = new Date(d);
  const day = x.getDay();
  const diff = day === 0 ? -6 : 1 - day;
  x.setDate(x.getDate() + diff);
  x.setHours(0, 0, 0, 0);
  return x.toISOString().slice(0, 10);
}

function parsePayrollISODate (iso) {
  if (!iso) return null;
  const parts = String(iso).slice(0, 10).split('-').map(Number);
  if (parts.length < 3) return null;
  const d = new Date(parts[0], parts[1] - 1, parts[2], 12, 0, 0, 0);
  return Number.isNaN(d.getTime()) ? null : d;
}

function addPayrollDays (iso, days) {
  const d = parsePayrollISODate(iso);
  if (!d) return iso;
  d.setDate(d.getDate() + days);
  return d.toISOString().slice(0, 10);
}

/** Semana laboral: lunes a domingo (7 días). */
function defaultPayrollWeekRange (ref = new Date()) {
  const weekStart = mondayOfWeek(ref);
  const weekEnd = addPayrollDays(weekStart, 6);
  return { weekStart, weekEnd };
}

function formatPayrollWeekRange (weekStart, weekEnd) {
  const fmt = (iso) => {
    const d = parsePayrollISODate(iso);
    if (!d) return '—';
    return d.toLocaleDateString('es-MX', {
      weekday: 'short', day: 'numeric', month: 'short', year: 'numeric',
    });
  };
  if (!weekStart && !weekEnd) return '—';
  if (weekStart && weekEnd && weekStart !== weekEnd) {
    return `${fmt(weekStart)} al ${fmt(weekEnd)}`;
  }
  return fmt(weekStart || weekEnd);
}

const PAY_TYPE_RATE_LABELS = {
  hour: 'Tarifa por hora (MXN / h)',
  day: 'Tarifa por día (MXN / día)',
  week: 'Tarifa por semana (MXN / semana)',
  fixed: 'Monto fijo por pago (MXN)',
};

const PAY_TYPE_HINTS = {
  hour: 'Captura las horas de cada día (Dom–Sáb). El total sugerido = horas × tarifa por hora.',
  day: 'Cada día con horas mayores a 0 cuenta como un día trabajado. Total = días × tarifa por día.',
  week: 'Si no capturas horas, se usa la tarifa semanal completa. Si capturas horas, se calcula horas × tarifa (útil cuando paga por hora dentro de la semana).',
  fixed: 'El total sugerido es siempre el monto fijo, sin importar las horas del calendario.',
};

async function fetchPayrollProfilesAdmin () {
  const sb = getSupabase();
  if (!sb) return [];
  const { data, error } = await sb
    .from('payroll_profiles')
    .select('*, coaches ( id, name, specialty, active )')
    .order('display_name', { ascending: true });
  if (error) {
    if (/payroll_profiles|schema cache|PGRST202/i.test(error.message || '')) {
      console.warn('[Tecos] payroll_profiles — ejecuta migración 062_finance_nomina_expenses.sql');
      return [];
    }
    throw error;
  }
  return data || [];
}

async function fetchExpenseCategoriesAdmin () {
  const sb = getSupabase();
  if (!sb) return [];
  const { data, error } = await sb
    .from('expense_categories')
    .select('*')
    .eq('active', true)
    .order('sort_order', { ascending: true });
  if (error) throw error;
  return data || [];
}

async function fetchExpensesAdmin (opts = {}) {
  const sb = getSupabase();
  if (!sb) return [];
  let q = sb
    .from('expenses')
    .select('*, expense_categories ( id, name )')
    .order('expense_at', { ascending: false });
  if (opts.from) q = q.gte('expense_at', opts.from);
  if (opts.to) q = q.lte('expense_at', opts.to);
  const { data, error } = await q.limit(opts.limit || 500);
  if (error) throw error;
  return data || [];
}

async function fetchPayrollPaymentsAdmin (opts = {}) {
  const sb = getSupabase();
  if (!sb) return [];
  let q = sb
    .from('payroll_payments')
    .select('*, payroll_profiles ( id, display_name, pay_type )')
    .order('paid_at', { ascending: false });
  if (opts.from) q = q.gte('paid_at', opts.from);
  if (opts.to) q = q.lte('paid_at', opts.to);
  const { data, error } = await q.limit(opts.limit || 500);
  if (error) throw error;
  return data || [];
}

async function fetchFinanceWithdrawalsAdmin (opts = {}) {
  const sb = getSupabase();
  if (!sb) return [];
  let q = sb.from('finance_withdrawals').select('*').order('withdrawn_at', { ascending: false });
  if (opts.from) q = q.gte('withdrawn_at', opts.from);
  if (opts.to) q = q.lte('withdrawn_at', opts.to);
  const { data, error } = await q.limit(opts.limit || 200);
  if (error) throw error;
  return data || [];
}

async function upsertPayrollProfile (payload) {
  const sb = requireSb();
  const row = {
    coach_id: payload.coach_id || null,
    display_name: String(payload.display_name || '').trim(),
    pay_type: payload.pay_type || 'week',
    rate_amount: Number(payload.rate_amount) || 0,
    pay_weekdays: Array.isArray(payload.pay_weekdays) ? payload.pay_weekdays.map(Number) : [],
    pay_days_of_month: Array.isArray(payload.pay_days_of_month) ? payload.pay_days_of_month.map(Number) : [],
    next_pay_date: payload.next_pay_date || null,
    notes: payload.notes?.trim() || null,
    active: payload.active !== false,
    weekly_hours: payload.weekly_hours != null
      ? (Array.isArray(payload.weekly_hours) ? payload.weekly_hours : normalizeWeeklyHoursForSave(payload.weekly_hours))
      : undefined,
    schedule_week_start: payload.schedule_week_start || null,
    updated_at: new Date().toISOString(),
  };
  if (row.weekly_hours === undefined) delete row.weekly_hours;
  if (!row.display_name) throw new Error('Nombre requerido');
  if (payload.id) {
    const { data, error } = await sb.from('payroll_profiles').update(row).eq('id', payload.id).select().single();
    if (error) throw error;
    notifyFinanceChanged();
    return data;
  }
  const { data, error } = await sb.from('payroll_profiles').insert(row).select().single();
  if (error) throw error;
  notifyFinanceChanged();
  return data;
}

/** Guardado rápido: horas por día de la semana sin abrir modal completo. */
async function savePayrollWeeklySchedule (profileId, schedule, scheduleWeekStart) {
  const sb = requireSb();
  const normalized = normalizeWeeklyHoursForSave(schedule);
  const { data, error } = await sb.from('payroll_profiles').update({
    weekly_hours: normalized,
    schedule_week_start: scheduleWeekStart || mondayOfWeek(),
    updated_at: new Date().toISOString(),
  }).eq('id', profileId).select().single();
  if (error) throw error;
  notifyFinanceChanged();
  return data;
}

async function deletePayrollProfile (id) {
  const sb = requireSb();
  const { error } = await sb.from('payroll_profiles').delete().eq('id', id);
  if (error) throw error;
  notifyFinanceChanged();
}

async function recordPayrollPayment ({ profile_id, amount, paid_at, concept, notes }) {
  const sb = requireSb();
  const { data, error } = await sb.from('payroll_payments').insert({
    profile_id,
    amount: Number(amount) || 0,
    paid_at: paid_at || new Date().toISOString(),
    concept: concept?.trim() || null,
    notes: notes?.trim() || null,
  }).select().single();
  if (error) throw error;
  notifyFinanceChanged();
  return data;
}

async function upsertExpenseCategory ({ id, name, sort_order }) {
  const sb = requireSb();
  const row = { name: String(name || '').trim(), sort_order: Number(sort_order) || 0, active: true };
  if (!row.name) throw new Error('Nombre de categoría requerido');
  if (id) {
    const { data, error } = await sb.from('expense_categories').update(row).eq('id', id).select().single();
    if (error) throw error;
    notifyFinanceChanged();
    return data;
  }
  const { data, error } = await sb.from('expense_categories').insert(row).select().single();
  if (error) throw error;
  notifyFinanceChanged();
  return data;
}

async function upsertExpense (payload) {
  const sb = requireSb();
  const row = {
    category_id: payload.category_id || null,
    name: String(payload.name || '').trim(),
    amount: Number(payload.amount) || 0,
    expense_at: payload.expense_at || new Date().toISOString(),
    is_variable: !!payload.is_variable,
    notes: payload.notes?.trim() || null,
    updated_at: new Date().toISOString(),
  };
  if (!row.name) throw new Error('Concepto del gasto requerido');
  if (payload.id) {
    const { data, error } = await sb.from('expenses').update(row).eq('id', payload.id).select().single();
    if (error) throw error;
    notifyFinanceChanged();
    return data;
  }
  const { data, error } = await sb.from('expenses').insert(row).select().single();
  if (error) throw error;
  notifyFinanceChanged();
  return data;
}

async function deleteExpense (id) {
  const sb = requireSb();
  const { error } = await sb.from('expenses').delete().eq('id', id);
  if (error) throw error;
  notifyFinanceChanged();
}

async function recordFinanceWithdrawal ({ amount, withdrawn_at, concept, notes }) {
  const sb = requireSb();
  const { data, error } = await sb.from('finance_withdrawals').insert({
    amount: Number(amount) || 0,
    withdrawn_at: withdrawn_at || new Date().toISOString(),
    concept: String(concept || '').trim() || 'Retiro',
    notes: notes?.trim() || null,
  }).select().single();
  if (error) throw error;
  notifyFinanceChanged();
  return data;
}

/** Crea perfiles de nómina para entrenadores activos que aún no tienen uno. */
async function syncCoachesToPayroll () {
  const sb = requireSb();
  const { data: coaches, error: cErr } = await sb.from('coaches').select('id, name, specialty, active').eq('active', true);
  if (cErr) throw cErr;
  const { data: existing, error: pErr } = await sb.from('payroll_profiles').select('coach_id');
  if (pErr) throw pErr;
  const haveCoach = new Set((existing || []).map((p) => p.coach_id).filter(Boolean));
  let created = 0;
  for (const c of coaches || []) {
    if (haveCoach.has(c.id)) continue;
    const { error } = await sb.from('payroll_profiles').insert({
      coach_id: c.id,
      display_name: c.name || 'Entrenador',
      pay_type: 'week',
      rate_amount: 0,
      notes: c.specialty ? `Especialidad: ${c.specialty}` : null,
    });
    if (!error) created += 1;
  }
  if (created) notifyFinanceChanged();
  return created;
}

/** Perfiles con pago mañana (aviso 1 día antes). */
function payrollProfilesDueTomorrow (profiles) {
  const tomorrow = new Date();
  tomorrow.setDate(tomorrow.getDate() + 1);
  const key = tomorrow.toISOString().slice(0, 10);
  return (profiles || []).filter((p) => {
    if (!p.active || !p.next_pay_date) return false;
    return String(p.next_pay_date).slice(0, 10) === key;
  });
}

function formatFinanceDateTime (iso) {
  if (!iso) return '—';
  const d = new Date(iso);
  if (Number.isNaN(d.getTime())) return '—';
  return d.toLocaleString('es-MX', {
    day: 'numeric', month: 'short', year: 'numeric',
    hour: '2-digit', minute: '2-digit',
  });
}

Object.assign(window, {
  PAY_TYPE_LABELS,
  PAY_TYPE_RATE_LABELS,
  PAY_TYPE_HINTS,
  WEEKDAY_LABELS,
  WEEKDAY_LABELS_FULL,
  emptyWeekSchedule,
  parseWeeklyHours,
  normalizeWeeklyHoursForSave,
  computePayrollWeekPreview,
  savePayrollWeeklySchedule,
  mondayOfWeek,
  parsePayrollISODate,
  addPayrollDays,
  defaultPayrollWeekRange,
  formatPayrollWeekRange,
  fetchPayrollProfilesAdmin,
  fetchExpenseCategoriesAdmin,
  fetchExpensesAdmin,
  fetchPayrollPaymentsAdmin,
  fetchFinanceWithdrawalsAdmin,
  upsertPayrollProfile,
  deletePayrollProfile,
  recordPayrollPayment,
  upsertExpenseCategory,
  upsertExpense,
  deleteExpense,
  recordFinanceWithdrawal,
  syncCoachesToPayroll,
  payrollProfilesDueTomorrow,
  formatFinanceDateTime,
  notifyFinanceChanged,
});
