<?php

require_once __DIR__ . '/persona.php';
require_once __DIR__ . '/utils.php';

const CHORE_SCHEDULE_ONCE = 'once';
const CHORE_SCHEDULE_RECURRING = 'recurring';
const CHORE_NFC_HASH_ALGO = 'sha256';

/**
 * @param mixed $raw
 * @return array<int, array<string, mixed>>
 */
function normalizeChoresList($raw): array {
    if (!is_array($raw) || !array_is_list($raw)) {
        return [];
    }
    $out = [];
    foreach ($raw as $row) {
        if (is_array($row) && !empty($row['id']) && is_string($row['id'])) {
            $out[] = $row;
        }
    }
    return $out;
}

/**
 * @param array<int, array<string, mixed>> $people
 * @return array<int, string>
 */
function legacyAssigneeNameToIds(string $name, array $people): array {
    $name = trim($name);
    if ($name === '') {
        return [];
    }
    $lower = familyHubStrLowerUtf8($name);
    $ids = [];
    foreach ($people as $p) {
        $pn = trim((string) ($p['name'] ?? ''));
        if ($pn !== '' && familyHubStrLowerUtf8($pn) === $lower) {
            $ids[] = (string) $p['id'];
        }
    }
    return $ids;
}

/**
 * @param array<string, mixed> $c
 * @param array<int, array<string, mixed>> $people
 * @return array<string, mixed>
 */
function migrateLegacyChoreRow(array $c, array $people): array {
    if (isset($c['name']) && !isset($c['title'])) {
        $c['title'] = (string) $c['name'];
    }
    if (!isset($c['assignee_ids']) || !is_array($c['assignee_ids'])) {
        if (isset($c['assignee'])) {
            $c['assignee_ids'] = legacyAssigneeNameToIds((string) $c['assignee'], $people);
        } else {
            $c['assignee_ids'] = [];
        }
    }
    $ids = [];
    foreach ($c['assignee_ids'] as $id) {
        if (is_string($id) && $id !== '') {
            $ids[] = $id;
        }
    }
    $c['assignee_ids'] = array_values(array_unique($ids));
    if (!isset($c['lists']) || !is_array($c['lists'])) {
        $c['lists'] = [];
    }
    $c['lists'] = normalizeChoreLists($c['lists']);
    if (!isset($c['description'])) {
        $c['description'] = '';
    }
    if (!isset($c['image'])) {
        $c['image'] = '';
    }
    if (!isset($c['author_id'])) {
        $c['author_id'] = '';
    }
    if (!isset($c['value'])) {
        $c['value'] = 0;
    }
    $c['value'] = is_numeric($c['value']) ? (float) $c['value'] : 0.0;
    if (!isset($c['due_date'])) {
        $c['due_date'] = '';
    }
    $c['due_date'] = is_string($c['due_date']) ? trim($c['due_date']) : '';
    if ($c['due_date'] !== '' && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $c['due_date'])) {
        $c['due_date'] = '';
    }
    $sched = $c['schedule'] ?? CHORE_SCHEDULE_ONCE;
    $c['schedule'] = $sched === CHORE_SCHEDULE_RECURRING ? CHORE_SCHEDULE_RECURRING : CHORE_SCHEDULE_ONCE;
    if (!isset($c['recurrence_days']) || !is_numeric($c['recurrence_days'])) {
        $c['recurrence_days'] = 7;
    }
    $c['recurrence_days'] = max(1, (int) $c['recurrence_days']);
    if (!isset($c['status'])) {
        $c['status'] = 'active';
    }
    $st = (string) $c['status'];
    $c['status'] = in_array($st, ['active', 'completed'], true) ? $st : 'active';
    if (!array_key_exists('pending_submission', $c)) {
        $c['pending_submission'] = null;
    }
    if ($c['pending_submission'] !== null && !is_array($c['pending_submission'])) {
        $c['pending_submission'] = null;
    }
    $c['anyone_can_complete'] = !empty($c['anyone_can_complete']);
    $c['nfc'] = normalizeChoreNfcMeta($c['nfc'] ?? null);
    return $c;
}

/**
 * @param mixed $raw
 * @return array<string, mixed>
 */
function normalizeChoreNfcMeta($raw): array {
    if (!is_array($raw)) {
        return [
            'token_hash' => '',
            'enabled' => false,
            'created_at' => '',
            'last_used_at' => null,
            'last_used_ip' => null,
        ];
    }
    $tokenHash = isset($raw['token_hash']) ? trim((string) $raw['token_hash']) : '';
    $createdAt = isset($raw['created_at']) ? trim((string) $raw['created_at']) : '';
    $enabled = !empty($raw['enabled']) && $tokenHash !== '';
    $lastUsedAt = isset($raw['last_used_at']) ? trim((string) $raw['last_used_at']) : '';
    $lastUsedIp = isset($raw['last_used_ip']) ? trim((string) $raw['last_used_ip']) : '';
    return [
        'token_hash' => $tokenHash,
        'enabled' => $enabled,
        'created_at' => $createdAt,
        'last_used_at' => $lastUsedAt !== '' ? $lastUsedAt : null,
        'last_used_ip' => $lastUsedIp !== '' ? $lastUsedIp : null,
    ];
}

function generateOpaqueToken(int $bytes = 24): string {
    $raw = random_bytes($bytes);
    return rtrim(strtr(base64_encode($raw), '+/', '-_'), '=');
}

function hashOpaqueToken(string $token): string {
    return hash(CHORE_NFC_HASH_ALGO, $token);
}

function validateTokenHash(string $token, string $storedHash): bool {
    if ($token === '' || $storedHash === '') {
        return false;
    }
    return hash_equals($storedHash, hashOpaqueToken($token));
}

function requestClientAddress(): ?string {
    $ip = trim((string) ($_SERVER['REMOTE_ADDR'] ?? ''));
    return $ip !== '' ? $ip : null;
}

/**
 * @param mixed $lists
 * @return array<int, array{type: string, items: array<int, string>}>
 */
function normalizeChoreLists($lists): array {
    if (!is_array($lists)) {
        return [];
    }
    $out = [];
    foreach ($lists as $block) {
        if (!is_array($block)) {
            continue;
        }
        $type = isset($block['type']) ? (string) $block['type'] : 'checkbox';
        if (!in_array($type, ['ordered', 'unordered', 'checkbox'], true)) {
            $type = 'checkbox';
        }
        $items = $block['items'] ?? [];
        if (!is_array($items)) {
            $items = [];
        }
        $clean = [];
        foreach ($items as $it) {
            $s = trim((string) $it);
            if ($s !== '') {
                $clean[] = $s;
            }
        }
        $out[] = ['type' => $type, 'items' => $clean];
    }
    return $out;
}

/**
 * @param array<int, array<string, mixed>> $chores
 * @return array<int, array<string, mixed>>
 */
function migrateAllChores(array $chores, array $people): array {
    $out = [];
    foreach ($chores as $c) {
        $out[] = migrateLegacyChoreRow($c, $people);
    }
    return $out;
}

/**
 * @param array<int, array<string, mixed>> $chores
 */
function findChoreById(array $chores, string $id): ?array {
    foreach ($chores as $c) {
        if (($c['id'] ?? '') === $id) {
            return $c;
        }
    }
    return null;
}

/**
 * @param array<int, array<string, mixed>> $chores
 */
function findChoreIndexById(array $chores, string $id): ?int {
    foreach ($chores as $i => $c) {
        if (($c['id'] ?? '') === $id) {
            return $i;
        }
    }
    return null;
}

/**
 * @param array<int, string> $assigneeIds
 * @param array<int, array<string, mixed>> $people
 */
function choreAssigneeIdsValid(array $assigneeIds, array $people): bool {
    $set = [];
    foreach ($people as $p) {
        if (!empty($p['id'])) {
            $set[(string) $p['id']] = true;
        }
    }
    foreach ($assigneeIds as $id) {
        if (!isset($set[$id])) {
            return false;
        }
    }
    return true;
}
