function parseCsvLine(line) {
  const values = [];
  let value = "";
  let quoted = false;

  for (let i = 0; i < line.length; i += 1) {
    const char = line[i];
    const next = line[i + 1];
    if (char === '"' && quoted && next === '"') {
      value += '"';
      i += 1;
    } else if (char === '"') {
      quoted = !quoted;
    } else if (char === "," && !quoted) {
      values.push(value);
      value = "";
    } else {
      value += char;
    }
  }
  values.push(value);
  return values;
}

function parseTickerCsv(csv) {
  const lines = String(csv || "")
    .split(/\r?\n/)
    .map(line => line.trim())
    .filter(Boolean);
  if (lines.length < 2) return [];

  const headers = parseCsvLine(lines[0]).map(header => header.trim());
  return lines.slice(1).map(line => {
    const values = parseCsvLine(line);
    return headers.reduce((row, header, index) => {
      row[header] = values[index] || "";
      return row;
    }, {});
  });
}

function firstValue(row, keys) {
  for (const key of keys) {
    const value = cleanTickerValue(row[key]);
    if (value) return value;
  }
  return "";
}

function isMissingTickerValue(value) {
  const text = String(value == null ? "" : value).trim();
  if (!text) return true;
  const normalized = text.toLowerCase().replace(/[_-]+/g, " ").replace(/\s+/g, " ");
  return [
    "missing data",
    "no data",
    "n/a",
    "na",
    "null",
    "undefined",
    "none",
    "-",
    "--"
  ].includes(normalized) || /\bmissing data\b/i.test(text);
}

function cleanTickerValue(value) {
  return isMissingTickerValue(value) ? "" : String(value == null ? "" : value).replace(/\s+/g, " ").trim();
}

function cleanClassName(value) {
  const text = cleanTickerValue(value);
  return text;
}

function cleanDriverName(value) {
  const text = cleanTickerValue(value);
  if (!text) return "";
  return text.replace(/[^\s]+/gu, token => {
    if (token.length <= 1) return token;
    const hasLetter = /\p{L}/u.test(token);
    if (!hasLetter || token !== token.toLocaleUpperCase() || token === token.toLocaleLowerCase()) return token;
    return token
      .split(/([-'’])/u)
      .map(part => /[-'’]/u.test(part) || !part ? part : part.charAt(0).toLocaleUpperCase() + part.slice(1).toLocaleLowerCase())
      .join("");
  });
}

function isLikelyVehicleDescription(value) {
  const text = cleanTickerValue(value).toLowerCase();
  if (!text) return false;
  const hasModelYear = /\b(?:19|20)\d{2}\b/.test(text);
  const vehicleWords = /\b(?:porsche|chevrolet|chevy|ford|toyota|honda|dodge|mustang|camaro|corvette|supra|bmw|audi|nissan|mazda|mercedes|clubsport|chassis|crate|engine|motor|modified|late model|street stock|sportsman|gt2|gt3|rs)\b/i;
  const specPattern = /\b(?:gt[234]|rs|zr1|z06|ss|lt1|ls[0-9])\b/i;
  return (hasModelYear && vehicleWords.test(text)) || /\bclubsport\b/i.test(text) || (specPattern.test(text) && vehicleWords.test(text));
}

const CAR_COLOR_ONLY_PATTERN = /^(?:(?:black|white|red|blue|green|yellow|orange|purple|silver|gold|gray|grey|maroon|pink|teal|navy|copper|chrome|charcoal|tan|cream|burgundy|lime|camo|neon)[\s\/&,.-]*)+$/i;

function isLikelyCarColor(value) {
  return CAR_COLOR_ONLY_PATTERN.test(String(value || "").trim());
}

function cleanHometown(value) {
  const text = cleanTickerValue(value);
  return isLikelyVehicleDescription(text) || isLikelyCarColor(text) ? "" : text;
}

function cleanLapValue(value) {
  const text = cleanTickerValue(value);
  if (!text) return "";
  if (/^(?:0+|0+\.0+)$/.test(text)) return "";
  if (/^0{1,2}:0{2}:0{2}(?:\.0+)?$/.test(text)) return "";
  return text;
}

function cleanLapsToGo(value) {
  const text = cleanTickerValue(value);
  const laps = Number(text);
  if (!Number.isFinite(laps) || laps >= 9999) return "";
  return text;
}

function sessionMode(meta) {
  const text = `${meta.className || ""} ${meta.raceName || ""}`.toLowerCase();
  const lapsToGo = Number(meta.lapsToGo);
  if (/\b(race|feature|heat|main|dash|b-main|a-main|competitors)\b/.test(text) || (Number.isFinite(lapsToGo) && lapsToGo > 0)) return "race";
  if (/\b(qualifying|time trials?|practice|hot\s*laps?|hotlaps|best lap)\b/.test(text)) return "qualifying";
  return "";
}

function numberedPositionSlots(row) {
  const slots = new Set();
  Object.keys(row).forEach(key => {
    const match = key.match(/^(?:Position|StartPos|FinishPos)(\d+)$/);
    if (match) slots.add(Number(match[1]));
  });
  return [...slots].sort((a, b) => a - b);
}

function slotPosition(row, slot) {
  return cleanTickerValue(row[`Position${slot}`])
    || cleanTickerValue(row[`StartPos${slot}`])
    || cleanTickerValue(row[`FinishPos${slot}`]);
}

function normalizeTickerRows(rows) {
  const competitors = [];
  let meta = {};

  rows.forEach((row, index) => {
    const trackCity = firstValue(row, ["TrackCity", "Track City", "VenueCity"]);
    const trackState = firstValue(row, ["TrackState", "Track State", "VenueState"]);
    const trackCountry = firstValue(row, ["TrackCountry", "Track Country", "VenueCountry"]);
    const trackLocation = [trackCity, trackState, trackCountry].filter(Boolean).join(", ")
      || firstValue(row, ["TrackLocation", "Track Location", "VenueLocation", "TrackAddress"]);
    meta = {
      trackName: firstValue(row, ["Track", "TrackName"]) || meta.trackName || "",
      weatherLocation: trackLocation || meta.weatherLocation || "",
      className: cleanClassName(firstValue(row, ["Class", "ClassName", "Division"])) || meta.className || "",
      raceName: firstValue(row, ["Race", "RaceName", "Event"]) || meta.raceName || "",
      flag: firstValue(row, ["FlagStatus", "Flag"]) || meta.flag || "",
      lapsToGo: cleanLapsToGo(firstValue(row, ["LapsToGo", "Laps To Go"])) || meta.lapsToGo || ""
    };
    meta.mode = sessionMode(meta) || meta.mode || "";

    const numberedSlots = numberedPositionSlots(row);

    if (numberedSlots.length) {
      numberedSlots.forEach(slot => {
        const number = cleanTickerValue(row[`Number${slot}`]);
        const firstName = cleanDriverName(row[`FirstName${slot}`]);
        const lastName = cleanDriverName(row[`LastName${slot}`]);
        const name = cleanDriverName(row[`FullName${slot}`]);
        const position = slotPosition(row, slot);
        const hometown = cleanHometown(row[`Hometown${slot}`] || row[`City${slot}`] || row[`HomeTown${slot}`]);
        const missingData = Boolean(position && !number && !name && !firstName && !lastName);
        if (!position && !number && !name && !firstName && !lastName) return;

        competitors.push({
          id: missingData ? `missing-${position || slot}` : `${position || competitors.length + 1}-${number || name}`,
          number,
          transponder: "",
          firstName,
          lastName,
          fallbackName: missingData ? "" : name,
          missingData,
          hometown,
          classNumber: "1",
          className: meta.className,
          position: position ? Number(position) : competitors.length + 1,
          laps: Number(row[`Laps${slot}`]) || null,
          bestLap: cleanLapValue(row[`BestLapTime${slot}`] || row[`BestLap${slot}`]),
          lastLap: cleanLapValue(row[`LastLapTime${slot}`]),
          gap: cleanTickerValue(row[`Diff${slot}`]),
          interval: "",
          status: meta.flag || ""
        });
      });
      return;
    }

    const number = firstValue(row, ["Number", "CarNumber", "Car", "No", "No.", "Nbr"]);
    const name = cleanDriverName(firstValue(row, ["Driver", "DriverName", "Name", "FullName"]));
    const firstName = cleanDriverName(firstValue(row, ["FirstName", "First"]));
    const lastName = cleanDriverName(firstValue(row, ["LastName", "Last"]));
    const position = firstValue(row, ["Position", "Pos", "Place", "Rank", "RunningOrder"]);
    const missingData = Boolean(position && !number && !name && !firstName && !lastName);

    if (!position && !number && !name && !firstName && !lastName) return;

    competitors.push({
      id: firstValue(row, ["ID", "DriverID", "DriverId"]) || number || name || `missing-${position || index + 1}`,
      number,
      transponder: "",
      firstName,
      lastName,
      fallbackName: missingData ? "" : name,
      missingData,
      hometown: cleanHometown(firstValue(row, ["Hometown", "City", "HomeTown"])),
      classNumber: firstValue(row, ["ClassID", "ClassId"]) || "1",
      className: meta.className,
      position: position ? Number(position) : index + 1,
      laps: Number(firstValue(row, ["Laps", "Lap"])) || null,
      bestLap: cleanLapValue(firstValue(row, ["BestLap", "BestLapTime", "Best"])),
      lastLap: cleanLapValue(firstValue(row, ["LastLap", "LastLapTime", "Last"])),
      gap: firstValue(row, ["Gap", "Diff"]),
      interval: firstValue(row, ["Interval"]),
      status: firstValue(row, ["Status"])
    });
  });

  return { meta, competitors };
}

module.exports = {
  parseTickerCsv,
  normalizeTickerRows
};
