<?php
$config = require __DIR__ . '/config.php';
date_default_timezone_set('Europe/Copenhagen');
$dataDir = $config['data_dir'];
$stateFile = "$dataDir/state.json";
$eventsFile = "$dataDir/events.jsonl";

function default_state(): array {
    return [
        'odometer' => 0,
        'updated_at' => null,
        'last' => [
            'oil_change' => null,
            'front_tyre_change' => null,
            'rear_tyre_change' => null,
            'shaft_oil_change' => null,
            'brake_pads' => null,
            'registration' => null,
        ],
        'latest_event' => null,
    ];
}
function load_state(string $file): array {
    if (!is_file($file)) return default_state();
    $s = json_decode(file_get_contents($file), true);
    return is_array($s) ? array_replace_recursive(default_state(), $s) : default_state();
}
function save_state(string $file, array $state): void {
    file_put_contents($file, json_encode($state, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), LOCK_EX);
}
function append_event(string $file, array $event): void {
    file_put_contents($file, json_encode($event, JSON_UNESCAPED_SLASHES) . "\n", FILE_APPEND | LOCK_EX);
}
function recent_events(string $file, int $limit = 20): array {
    if (!is_file($file)) return [];
    $lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
    $lines = array_slice($lines, -$limit);
    $events = [];
    foreach (array_reverse($lines) as $line) {
        $e = json_decode($line, true);
        if (is_array($e)) $events[] = $e;
    }
    return $events;
}
function km_since(array $state, string $key): ?int {
    $last = $state['last'][$key] ?? null;
    if (!is_array($last) || !isset($last['km'])) return null;
    return max(0, (int)$state['odometer'] - (int)$last['km']);
}
function ownership_text(array $config): string {
    $date = $config['purchase_date'] ?? null;
    if (!$date) return '';
    try { $start = new DateTime($date); } catch (Throwable $e) { return ''; }
    $now = new DateTime('today');
    $diff = $start->diff($now);
    $parts = [];
    if ($diff->y) $parts[] = $diff->y . 'y';
    if ($diff->m) $parts[] = $diff->m . 'm';
    if (!$parts || $diff->d) $parts[] = $diff->d . 'd';
    return implode(' ', $parts);
}
function eu_date(?string $date): string {
    if (!$date) return '';
    try { return (new DateTime($date))->setTimezone(new DateTimeZone('Europe/Copenhagen'))->format('d.m.Y'); }
    catch (Throwable $e) { return $date; }
}
function mqtt_pub(array $config, string $topic, string $payload, bool $retain = true): void {
    $host = $config['mqtt_host'];
    $port = (int)($config['mqtt_port'] ?? 1883);
    $cmd = 'mosquitto_pub -h ' . escapeshellarg($host)
        . ' -p ' . escapeshellarg((string)$port)
        . ' -t ' . escapeshellarg($topic)
        . ' -m ' . escapeshellarg($payload)
        . ($retain ? ' -r' : '');
    @shell_exec($cmd . ' 2>/dev/null');
}
function publish_discovery(array $config): void {
    $p = trim($config['topic_prefix'], '/');
    $dev = ['identifiers' => ['fjr_tracker'], 'name' => $config['bike_name'], 'manufacturer' => 'Yamaha', 'model' => 'FJR'];
    $sensors = [
        'odometer' => ['name' => 'Odometer', 'unit' => 'km', 'topic' => "$p/odometer"],
        'km_since_bought' => ['name' => 'Km since bought', 'unit' => 'km', 'topic' => "$p/km_since_bought"],

        'oil_last_km' => ['name' => 'Oil last change km', 'unit' => 'km', 'topic' => "$p/oil/last_change_km"],
        'oil_last_date' => ['name' => 'Oil last change date', 'unit' => '', 'topic' => "$p/oil/last_change_date"],
        'oil_km_since_change' => ['name' => 'Oil km since change', 'unit' => 'km', 'topic' => "$p/oil/km_since_change"],

        'front_tyre_last_km' => ['name' => 'Front tyre last change km', 'unit' => 'km', 'topic' => "$p/tyres/front/last_change_km"],
        'front_tyre_last_date' => ['name' => 'Front tyre last change date', 'unit' => '', 'topic' => "$p/tyres/front/last_change_date"],
        'front_tyre_km_since_change' => ['name' => 'Front tyre km since change', 'unit' => 'km', 'topic' => "$p/tyres/front/km_since_change"],

        'rear_tyre_last_km' => ['name' => 'Rear tyre last change km', 'unit' => 'km', 'topic' => "$p/tyres/rear/last_change_km"],
        'rear_tyre_last_date' => ['name' => 'Rear tyre last change date', 'unit' => '', 'topic' => "$p/tyres/rear/last_change_date"],
        'rear_tyre_km_since_change' => ['name' => 'Rear tyre km since change', 'unit' => 'km', 'topic' => "$p/tyres/rear/km_since_change"],

        'shaft_oil_last_km' => ['name' => 'Drive shaft oil last change km', 'unit' => 'km', 'topic' => "$p/shaft_oil/last_change_km"],
        'shaft_oil_last_date' => ['name' => 'Drive shaft oil last change date', 'unit' => '', 'topic' => "$p/shaft_oil/last_change_date"],
        'shaft_oil_km_since_change' => ['name' => 'Drive shaft oil km since change', 'unit' => 'km', 'topic' => "$p/shaft_oil/km_since_change"],

        'brake_pads_last_km' => ['name' => 'Brake pads last change km', 'unit' => 'km', 'topic' => "$p/brakes/last_pads_km"],
        'brake_pads_last_date' => ['name' => 'Brake pads last change date', 'unit' => '', 'topic' => "$p/brakes/last_pads_date"],
        'brake_pads_km_since_change' => ['name' => 'Brake pads km since change', 'unit' => 'km', 'topic' => "$p/brakes/km_since_pads"],

    ];
    foreach ($sensors as $id => $sensor) {
        $payload = ['name' => $sensor['name'], 'state_topic' => $sensor['topic'], 'unique_id' => "fjr_$id", 'device' => $dev];
        if ($sensor['unit'] !== '') $payload['unit_of_measurement'] = $sensor['unit'];
        mqtt_pub($config, "homeassistant/sensor/fjr_$id/config", json_encode($payload, JSON_UNESCAPED_SLASHES));
    }
}

function publish_service_state(array $config, string $baseTopic, array $state, string $key, string $lastKmTopic, string $lastDateTopic, string $sinceTopic): void {
    $p = trim($config['topic_prefix'], '/');
    $last = $state['last'][$key] ?? null;
    if (is_array($last) && isset($last['km'])) {
        mqtt_pub($config, "$p/$lastKmTopic", (string)(int)$last['km']);
        mqtt_pub($config, "$p/$lastDateTopic", eu_date($last['time'] ?? ''));
        $v = km_since($state, $key);
        mqtt_pub($config, "$p/$sinceTopic", $v === null ? '' : (string)$v);
    } else {
        mqtt_pub($config, "$p/$lastKmTopic", 'unknown');
        mqtt_pub($config, "$p/$lastDateTopic", 'unknown');
        mqtt_pub($config, "$p/$sinceTopic", 'unknown');
    }
}
function publish_state(array $config, array $state): void {
    $p = trim($config['topic_prefix'], '/');
    publish_discovery($config);
    $purchaseKm = (int)($config['purchase_km'] ?? 0);
    $kmOwned = max(0, (int)$state['odometer'] - $purchaseKm);

    mqtt_pub($config, "$p/odometer", (string)(int)$state['odometer']);
    mqtt_pub($config, "$p/km_since_bought", (string)$kmOwned);

    publish_service_state($config, $p, $state, 'oil_change', 'oil/last_change_km', 'oil/last_change_date', 'oil/km_since_change');
    publish_service_state($config, $p, $state, 'front_tyre_change', 'tyres/front/last_change_km', 'tyres/front/last_change_date', 'tyres/front/km_since_change');
    publish_service_state($config, $p, $state, 'rear_tyre_change', 'tyres/rear/last_change_km', 'tyres/rear/last_change_date', 'tyres/rear/km_since_change');
    publish_service_state($config, $p, $state, 'shaft_oil_change', 'shaft_oil/last_change_km', 'shaft_oil/last_change_date', 'shaft_oil/km_since_change');
    publish_service_state($config, $p, $state, 'brake_pads', 'brakes/last_pads_km', 'brakes/last_pads_date', 'brakes/km_since_pads');

    mqtt_pub($config, "$p/state", json_encode($state, JSON_UNESCAPED_SLASHES));
}


$state = load_state($stateFile);
$message = '';
$types = [
    'odometer' => 'Odometer update only',
    'oil_change' => 'Oil change',
    'front_tyre_change' => 'Front tyre changed',
    'rear_tyre_change' => 'Rear tyre changed',
    'both_tires_change' => 'Both tires changed',
    'shaft_oil_change' => 'Drive shaft oil change',
    'brake_pads' => 'Brake pads',
    'registration' => 'Registration / insurance',
    'fuel' => 'Fuel',
    'note' => 'Other note',
];

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $km = max(0, (int)($_POST['km'] ?? 0));
    $type = $_POST['type'] ?? 'odometer';
    if (!isset($types[$type])) $type = 'note';
    $cost = trim($_POST['cost'] ?? '');
    $note = trim($_POST['note'] ?? '');
    $eventDate = trim($_POST['event_date'] ?? '');
    $eventTime = date('c');
    if ($eventDate !== '') {
        $dt = DateTime::createFromFormat('Y-m-d', $eventDate) ?: DateTime::createFromFormat('Y-m-d\TH:i', $eventDate);
        if ($dt instanceof DateTime) $eventTime = $dt->format('c');
    }
    if ($km >= (int)$state['odometer']) $state['odometer'] = $km;
    $event = [
        'time' => $eventTime,
        'type' => $type,
        'label' => $types[$type],
        'km' => $km,
        'cost' => $cost === '' ? null : $cost,
        'note' => $note,
    ];
    $serviceKeys = $type === 'both_tires_change' ? ['front_tyre_change', 'rear_tyre_change'] : [$type];
    foreach ($serviceKeys as $serviceKey) {
        if (array_key_exists($serviceKey, $state['last'])) {
            $prev = $state['last'][$serviceKey] ?? null;
            if (!is_array($prev) || $km >= (int)($prev['km'] ?? 0)) $state['last'][$serviceKey] = $event;
        }
    }
    $event['summary'] = $types[$type] . ' @ ' . $km . ' km' . ($note ? ' - ' . $note : '');
    $state['latest_event'] = $event;
    $state['updated_at'] = date('c');
    save_state($stateFile, $state);
    append_event($eventsFile, $event);
    publish_state($config, $state);
    $message = 'Saved and published to MQTT.';
}
$events = recent_events($eventsFile);
function h($s): string { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
function event_class(?string $type): string {
    return match ($type) {
        'odometer' => 'event-odometer',
        'oil_change' => 'event-oil',
        'front_tyre_change', 'rear_tyre_change', 'both_tires_change' => 'event-tyre',
        'shaft_oil_change' => 'event-shaft',
        'brake_pads' => 'event-brakes',
        'registration' => 'event-registration',
        'fuel' => 'event-fuel',
        default => 'event-note',
    };
}
?>
<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<title>My <?=h($config['bike_name'])?></title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="apple-touch-icon" href="/favicon.svg">
<style>
:root{color-scheme:dark;--bg:#0b0b0b;--card:#1b1b1b;--field:#101010;--line:#333;--text:#f2f2f2;--muted:#aaa;--green:#2f8f3a;--blue:#2d6cdf;--orange:#d9822b}
*{box-sizing:border-box}body{font-family:system-ui,-apple-system,Segoe UI,sans-serif;margin:0;background:linear-gradient(rgba(11,11,11,.82),rgba(11,11,11,.92)),url('/img/fjr.jpg') center top/cover fixed no-repeat,var(--bg);color:var(--text);font-size:16px;-webkit-text-size-adjust:100%}.wrap{max-width:850px;margin:auto;padding:14px;padding-bottom:34px}h1{font-size:26px;margin:4px 0 12px}.topbar{display:flex;align-items:center;gap:12px}.logo{width:64px;height:64px;object-fit:contain;border-radius:0;background:transparent;padding:0;border:0;box-shadow:none}h2{font-size:20px;margin:0 0 12px}.card{background:rgba(27,27,27,.88);backdrop-filter:blur(3px);border:1px solid var(--line);border-radius:18px;padding:16px;margin:12px 0;box-shadow:0 8px 24px #0004}label{display:block;margin-top:14px;margin-bottom:6px;color:#d0d0d0;font-weight:650}input,select,textarea,button{width:100%;font:inherit;font-size:18px;padding:15px 14px;border-radius:14px;border:1px solid #444;background:var(--field);color:var(--text)}select{min-height:54px}textarea{resize:vertical}button{background:var(--green);border:0;margin-top:16px;font-weight:800;min-height:56px;touch-action:manipulation}button:disabled{background:#3a3a3a;color:#888;cursor:not-allowed;opacity:.75}.bikePhoto{width:100%;max-height:330px;object-fit:cover;border-radius:18px;border:1px solid var(--line);box-shadow:0 8px 24px #0006;margin:0 0 12px}.hero{text-decoration:none;color:inherit;display:grid;grid-template-columns:1.4fr 1fr 1fr;gap:10px;align-items:stretch}.heroBox{background:rgba(16,16,16,.84);border:1px solid #2a2a2a;border-radius:14px;padding:13px}.heroBox.main{border-color:#3b5f45}.icon{font-size:22px;margin-right:6px}.grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.stat{background:rgba(16,16,16,.84);border:1px solid #2a2a2a;border-radius:14px;padding:13px;min-height:112px}.big{font-size:30px;line-height:1.05;font-weight:850}.ok{background:#17351a;border-color:var(--green)}.muted{color:var(--muted);font-size:13px}.historyList{display:grid;gap:10px}.historyItem{position:relative;overflow:hidden;background:rgba(16,16,16,.82);border:1px solid #2a2a2a;border-left:5px solid #555;border-radius:14px;padding:12px 13px;box-shadow:0 4px 14px #0003}.historyItem:before{content:"";position:absolute;inset:0;background:linear-gradient(90deg,var(--event-glow,transparent),transparent 46%);opacity:.16;pointer-events:none}.historyNote{position:relative;color:#f2f2f2;font-size:16px;font-weight:750;line-height:1.35;overflow-wrap:anywhere}.historyNote.emptyNote{color:var(--muted);font-weight:650;font-style:italic}.historyMeta{position:relative;display:flex;align-items:center;flex-wrap:wrap;gap:7px;margin-top:9px}.eventType{display:inline-flex;align-items:center;border-radius:999px;padding:5px 9px;background:#303030;color:#f5f5f5;font-size:13px;font-weight:800;line-height:1.1}.historyDate{color:var(--muted);font-size:13px}.historyKm{margin-left:auto;font-size:16px;font-weight:900;white-space:nowrap;color:#fff}.event-odometer{border-left-color:#555;--event-glow:#555}.event-oil{border-left-color:var(--orange);--event-glow:#d9822b}.event-tyre{border-left-color:#8b7cf6;--event-glow:#8b7cf6}.event-shaft{border-left-color:#b7a05b;--event-glow:#b7a05b}.event-brakes{border-left-color:#dc3545;--event-glow:#dc3545}.event-registration{border-left-color:var(--green);--event-glow:#2f8f3a}.event-fuel{border-left-color:#00a896;--event-glow:#00a896}.event-note{border-left-color:#777;--event-glow:#777}.event-odometer .eventType{background:#18325f}.event-oil .eventType{background:#553113}.event-tyre .eventType{background:#332b64}.event-shaft .eventType{background:#4a4124}.event-brakes .eventType{background:#561b22}.event-registration .eventType{background:#17351a}.event-fuel .eventType{background:#0f463d}.event-note .eventType{background:#333}.emptyHistory{color:var(--muted);padding:8px 0}
@media (max-width:520px){.logo{width:52px;height:52px}.wrap{padding:10px}.card{border-radius:16px;padding:14px;margin:10px 0}h1{font-size:24px}.hero{grid-template-columns:1fr}.grid{grid-template-columns:1fr 1fr;gap:8px}.stat{padding:11px;min-height:104px}.stat b{font-size:15px}.big{font-size:26px}input,select,textarea,button{font-size:17px;padding:14px 12px}.historyNote{font-size:17px}.historyKm{margin-left:0;font-size:18px}}
@media (max-width:360px){.grid{grid-template-columns:1fr}.big{font-size:28px}}
</style>
</head><body><div class="wrap">
<?php if ($message): ?><div class="card ok"><?=h($message)?></div><?php endif; ?>
<?php
$purchaseKm = (int)($config['purchase_km'] ?? 0);
$kmOwned = max(0, (int)$state['odometer'] - $purchaseKm);
$ownedFor = ownership_text($config);
$icons = [
    'oil_change' => '🛢️',
    'front_tyre_change' => '◉',
    'rear_tyre_change' => '◉',
    'shaft_oil_change' => '⚙️',
    'brake_pads' => '🛑',
    'registration' => '📄',
];
$statCards = [];
foreach ([ 'oil_change'=>'Oil', 'front_tyre_change'=>'Front tyre', 'rear_tyre_change'=>'Rear tyre', 'shaft_oil_change'=>'Drive shaft oil', 'brake_pads'=>'Brake pads', 'registration'=>'Registration'] as $key=>$label) {
    $v = km_since($state, $key);
    $last = $state['last'][$key]['km'] ?? null;
    $statCards[$key] = ['icon'=>$icons[$key], 'label'=>$label, 'value'=>$v===null ? '—' : number_format($v), 'suffix'=>$v===null ? '' : 'km', 'last'=>'last: ' . ($last===null ? 'never' : number_format((int)$last).' km')];
}
?>
<a class="card hero" href="https://fjr.novosel.dk">
  <div class="heroBox main"><div class="muted">🏍️ Current odometer</div><div class="big"><?=number_format((int)$state['odometer'])?> km</div><div class="muted">Yamaha FJR <?=h($config['production_year'] ?? '')?> · Reg: <?=h($config['registration_number'] ?? '')?></div></div>
  <div class="heroBox"><div class="muted">📍 Since bought</div><div class="big"><?=number_format($kmOwned)?></div><div class="muted">km since <?=h(eu_date($config['purchase_date'] ?? ''))?></div></div>
  <div class="heroBox"><div class="muted">📅 Owned for</div><div class="big"><?=h($ownedFor)?></div><div class="muted">bought @ <?=number_format($purchaseKm)?> km</div></div>
</a>
<div class="grid">
<?php foreach ($statCards as $card): ?>
<div class="stat"><b><span class="icon"><?=$card['icon']?></span><?=$card['label']?></b><br><span class="big"><?=$card['value']?></span> <span class="muted"><?=$card['suffix']?></span><br><span class="muted"><?=$card['last']?></span></div>
<?php endforeach; ?>
</div>
<div class="card"><h2>Add update</h2><form method="post" id="updateForm">
<label>Current km / event km</label><input name="km" id="kmInput" type="number" inputmode="numeric" min="<?=h((int)$state['odometer'] + 1)?>" step="1" data-current-km="<?=h($state['odometer'])?>" placeholder="> <?=h(number_format((int)$state['odometer']))?>" required>
<label>Date</label><input name="event_date" type="date" value="<?=h(date('Y-m-d'))?>" required>
<label>Type</label><select name="type"><?php foreach ($types as $k=>$v): ?><option value="<?=h($k)?>"><?=h($v)?></option><?php endforeach; ?></select>
<label>Cost (optional)</label><input name="cost" placeholder="e.g. 42.50">
<label>Note (optional)</label><textarea name="note" rows="3" placeholder="brand, details, reminder..."></textarea>
<button id="saveButton" disabled>Save</button>
</form></div>
<div class="card"><h2>Recent history</h2><div class="historyList">
<?php if (!$events): ?>
  <div class="emptyHistory">No history yet.</div>
<?php endif; ?>
<?php foreach ($events as $e): $note = trim((string)($e['note'] ?? '')); $isOdometer = ($e['type'] ?? '') === 'odometer'; ?>
  <article class="historyItem <?=h(event_class($e['type'] ?? null))?>">
    <div class="historyNote<?=$note === '' ? ' emptyNote' : ''?>"><?=h($note === '' ? 'No note' : $note)?></div>
    <div class="historyMeta">
      <?php if (!$isOdometer): ?><span class="eventType"><?=h($e['label']??$e['type']??'')?></span><?php endif; ?>
      <span class="historyDate"><?=h(eu_date($e['time'] ?? ''))?></span>
      <span class="historyKm"><?=number_format((int)($e['km']??0))?> km</span>
    </div>
  </article>
<?php endforeach; ?>
</div></div>
</div><script>
(() => {
  const kmInput = document.getElementById('kmInput');
  const saveButton = document.getElementById('saveButton');
  if (!kmInput || !saveButton) return;
  const currentKm = Number(kmInput.dataset.currentKm || 0);
  const updateSaveState = () => {
    const rawValue = kmInput.value.trim();
    const enteredKm = Number(rawValue);
    saveButton.disabled = rawValue === '' || !Number.isFinite(enteredKm) || enteredKm <= currentKm;
  };
  kmInput.addEventListener('input', updateSaveState);
  updateSaveState();
})();
</script></body></html>
