(() => {
    try {
        if (localStorage.getItem('tm_session') !== 'ok') {
            const next = encodeURIComponent(location.pathname + location.search);
            location.href = `/login.html?next=${next}`;
        }
    } catch (_) { }
})();

const { createApp, ref, onMounted, onUnmounted, computed, reactive, watch } = Vue;

createApp({
    setup() {
        const detectInitialView = () => {
            const view = new URLSearchParams(location.search).get('view');
            if (view) return view;
            if (location.pathname === '/admin/ic-card' || location.pathname === '/ic-card-admin') return 'iccards';
            return 'dashboard';
        };
        const currentView = ref(detectInitialView());
        const sidebarOpen = ref(false);
        const viewTitle = computed(() => {
            const map = {
                dashboard: '仪表盘',
                management: '线路与票价管理',
                faremap: '票价地图',
                tickets: '车票记录',
                vouchers: '凭证管理',
                iccards: 'IC 卡管理',
                assets: '线路图',
                settings: '系统设置',
                logs: '日志'
            };
            return map[currentView.value] || '票价图';
        });

        const connected = ref(false);
        const socket = io({ transports: ['websocket', 'polling'], upgrade: false });

        // Data State
        const stations = ref([]);
        const lines = ref([]);
        const fares = ref([]);
        const tickets = ref([]);
        const stats = reactive({ sold_tickets: 0, revenue: 0 });
        const config = reactive({ api_base: '', promotion: { name: '', discount: 1 } });
        const logs = ref([]);
        const logCategory = ref('');
        const logTypeFilter = ref('');
        const logQuery = ref('');
        const logMax = ref(200);
        const logLoading = ref(false);
        const orders = ref([]);
        const assetsManifest = reactive({ routeMap: null, fareTable: null, updatedAt: null });
        const assetsFarePreview = reactive({ headers: [], rows: [] });
        const assetsRouteMapUrl = ref('');
        const assetsFareTableUrl = ref('');
        const icCards = ref([]);
        const icCardSearch = ref('');
        const icSelectedId = ref('');
        const icSelectedCard = ref(null);
        const icSelectedEvents = ref([]);
        const icCreateForm = reactive({ holder_name: '', balance: 50 });
        const icDetailForm = reactive({ holder_name: '', status: 'active' });
        let icCardSyncTimer = null;
        
        // UI State
        const showAddLine = ref(false);
        const showAddStation = ref(false);
        const newLine = reactive({ id: '', name: '', en_name: '', color: '#3366cc' });
        const newStation = reactive({ code: '', name: '', en_name: '' });
        
        // Ticket View State
        const showTicketModal = ref(false);
        const selectedTicket = ref(null);
        
        // Management View State
        const selectedLine = ref(null);
        const fareMode = ref(false);
        const stationEditMode = ref(false);
        const fareSelection = ref([]);
        const showFareModal = ref(false);
        const currentFare = reactive({ exists: false, cost_regular: 0, cost_express: 0 });
        const draggingStationIndex = ref(null);
        const showStationModal = ref(false);
        const stationForm = reactive({ code: '', name: '', en_name: '', transfer_enabled: false, transfer_to: [] });
        const stationFormOriginalCode = ref('');
        const showLineModal = ref(false);
        const lineFormOriginalId = ref('');
        const lineForm = reactive({ id: '', name: '', en_name: '', color: '#3366cc', stations: [] });
        
        // Legacy/Other State
        const fareMapSvg = ref('');
        const fareMapScale = ref(1);
        const fareMapLoading = ref(false);
        const fareMapError = ref('');
        const ticketSearch = ref('');
        const lastActionError = ref('');
        const lastActionOkTs = ref(0);
        const mutationBusy = ref(false);
        const appDialog = window.appDialog || {
            alert: (message) => Promise.resolve(window.alert(message)),
            confirm: (message) => Promise.resolve(window.confirm(message)),
            prompt: (message, defaultValue) => Promise.resolve(window.prompt(message, defaultValue))
        };

        const buildAssetUrl = (name) => {
            if (!name) return '';
            const v = assetsManifest.updatedAt ? String(assetsManifest.updatedAt) : String(Date.now());
            return `/assets/${encodeURIComponent(name)}?v=${encodeURIComponent(v)}`;
        };

        const parseJsonSafe = (text) => {
            if (text == null) return null;
            const t = String(text);
            if (!t) return null;
            try { return JSON.parse(t); } catch (e) { return null; }
        };

        const parseCsvSimple = (text) => {
            const out = { headers: [], rows: [] };
            const raw = String(text || '').replace(/\r\n/g, '\n').replace(/\r/g, '\n');
            const lines = raw.split('\n').map(x => x.trim()).filter(Boolean);
            if (!lines.length) return out;
            const splitLine = (line) => {
                const parts = [];
                let cur = '';
                let inQ = false;
                for (let i = 0; i < line.length; i++) {
                    const ch = line[i];
                    if (ch === '"') {
                        if (inQ && line[i + 1] === '"') { cur += '"'; i++; continue; }
                        inQ = !inQ;
                        continue;
                    }
                    if (!inQ && ch === ',') { parts.push(cur); cur = ''; continue; }
                    cur += ch;
                }
                parts.push(cur);
                return parts.map(x => x.trim());
            };
            const headers = splitLine(lines[0]).filter(Boolean);
            if (!headers.length) return out;
            out.headers = headers;
            for (let i = 1; i < lines.length; i++) {
                const cols = splitLine(lines[i]);
                const row = {};
                for (let j = 0; j < headers.length; j++) row[headers[j]] = (cols[j] == null ? '' : cols[j]);
                out.rows.push(row);
            }
            return out;
        };

        const requestJson = async (url, opts = {}, { expectOk = false, timeoutMs = 15000 } = {}) => {
            const controller = new AbortController();
            const timer = setTimeout(() => controller.abort(), timeoutMs);
            try {
                const r = await fetch(url, { ...opts, signal: controller.signal });
                const text = await r.text();
                const data = parseJsonSafe(text) ?? (text ? { raw: text } : null);
                if (!r.ok) {
                    const msg = (data && (data.error || data.错误)) || r.statusText || '请求失败';
                    throw new Error(`${r.status} ${msg}`);
                }
                if (expectOk) {
                    if (data && data.ok === false) throw new Error(data.error || data.错误 || '操作失败');
                    if (data && data.ok == null && (data.error || data.错误)) throw new Error(data.error || data.错误);
                }
                return data;
            } catch (e) {
                const msg = (e && e.name === 'AbortError') ? '请求超时' : (e?.message || String(e));
                throw new Error(msg);
            } finally {
                clearTimeout(timer);
            }
        };

        const runMutation = async (action, { successMessage } = {}) => {
            if (mutationBusy.value) return;
            mutationBusy.value = true;
            lastActionError.value = '';
            try {
                await action();
                lastActionOkTs.value = Date.now();
                if (successMessage) alert(successMessage);
                await fetchData();
            } catch (e) {
                lastActionError.value = e?.message || String(e);
                alert(`操作失败：${lastActionError.value}`);
                await fetchData();
            } finally {
                mutationBusy.value = false;
            }
        };

        // Methods
        const formatTime = (ts) => {
            if (ts == null || ts === '') return '---';
            let value = Number(ts);
            if (Number.isFinite(value)) {
                if (value > 0 && value < 1000000000000) value *= 1000;
                const d = new Date(value);
                if (!Number.isNaN(d.getTime())) {
                    return d.toLocaleString('zh-CN', { hour12: false });
                }
            }
            const d = new Date(ts);
            if (!Number.isNaN(d.getTime())) {
                return d.toLocaleString('zh-CN', { hour12: false });
            }
            return String(ts);
        };
        
        const formatLogType = (type) => {
            const map = {
                'update_config_generic': { icon: 'fa-cog', text: '配置更新', class: 'text-primary' },
                'update_station': { icon: 'fa-map-marker-alt', text: '站点更新', class: 'text-info' },
                'add_line': { icon: 'fa-plus-circle', text: '新增线路', class: 'text-success' },
                'update_line': { icon: 'fa-edit', text: '线路更新', class: 'text-warning' },
                'update_fare': { icon: 'fa-coins', text: '票价更新', class: 'text-warning' },
                'delete_fare': { icon: 'fa-trash', text: '票价删除', class: 'text-danger' },
                'ticket_sold': { icon: 'fa-ticket-alt', text: '售票成功', class: 'text-success' },
                'gate_entry': { icon: 'fa-sign-in-alt', text: '进站', class: 'text-info' },
                'gate_exit': { icon: 'fa-sign-out-alt', text: '出站', class: 'text-info' }
            };
            return map[type] || { icon: 'fa-info-circle', text: type, class: 'text-muted' };
        };
        
        const formatTicketStatus = (status) => {
             const map = {
                 'valid': { text: '有效', class: 'badge-success' },
                 'used': { text: '已使用', class: 'badge-secondary' },
                 'expired': { text: '已过期', class: 'badge-danger' },
                 'refunded': { text: '已退票', class: 'badge-warning' }
             };
             return map[status] || { text: status || '未知', class: 'badge-secondary' };
        };

        const formatTrainType = (type) => {
            if (!type) return '普通';
            const t = type.toLowerCase();
            if (t === 'local') return '普通';
            if (t === 'ltd.exp' || t === 'express') return '特急';
            return type;
        };

        const getTicketEventType = (event) => String((event && (event["类型"] || event.type)) || '').toLowerCase();

        const getTicketEventAction = (event) => String((event && (event["动作"] || event.action)) || '').toLowerCase();

        const formatTicketEvent = (eventOrType) => {
            const event = eventOrType && typeof eventOrType === 'object' ? eventOrType : { type: eventOrType };
            const type = getTicketEventType(event);
            const action = getTicketEventAction(event);
            if (type === 'sale' || type === '售票') return '售票成功';
            if (type === 'entry' || action === 'entry') return '进站成功';
            if (type === 'exit' || action === 'exit') return '出站成功';
            if (type === 'status' || type === '状态') {
                return { entry: '进站成功', exit: '出站成功' }[action] || '状态变更';
            }
            const map = {
                'entry': '进站',
                'exit': '出站',
                'check': '验票',
                'status': '状态变更',
                'refund': '退票'
            };
            return map[type] || event["类型"] || event.type || '状态变更';
        };

        const formatTicketEventLocation = (event) => {
            const type = getTicketEventType(event);
            const stationName = event?.["售票站"] || event?.["发生站"] || event?.station_name || '';
            const stationCode = event?.["站点编号"] || event?.station_code || '';
            if (type === 'sale' || type === '售票') {
                return stationName || '线上售票';
            }
            if (!stationName && !stationCode) return '---';
            return [stationName, stationName && stationCode ? stationCode : ''].filter(Boolean).join(' ');
        };

        const formatTicketEventAttachment = (value) => {
            if (value == null || value === '') return '';
            if (typeof value === 'string') return value;
            if (typeof value === 'number' || typeof value === 'boolean') return String(value);
            if (Array.isArray(value)) {
                return value.map(formatTicketEventAttachment).filter(Boolean).join(' | ');
            }
            if (typeof value === 'object') {
                return Object.entries(value)
                    .filter(([, item]) => item != null && item !== '')
                    .map(([key, item]) => `${key}: ${formatTicketEventAttachment(item)}`)
                    .filter(Boolean)
                    .join(' | ');
            }
            return String(value);
        };

        const formatTicketEventExtra = (event) => {
            const type = getTicketEventType(event);
            const amount = event?.["售票额"] ?? event?.amount ?? event?.price ?? event?.cost;
            const stationEn = event?.["站点英文"] || event?.station_en || '';
            const deviceId = event?.["设备编号"] || event?.device_id || event?.device || '';
            const attachment = formatTicketEventAttachment(
                event?.["附加信息"] ?? event?.extra ?? event?.info ?? event?.meta ?? event?.detail
            );
            const parts = [];
            if ((type === 'sale' || type === '售票') && amount != null && amount !== '') {
                parts.push(`票价：¥ ${amount}`);
            }
            if (stationEn && deviceId) parts.push(`${stationEn} (${deviceId})`);
            else if (deviceId) parts.push(`设备：${deviceId}`);
            else if (stationEn) parts.push(stationEn);
            if (attachment) parts.push(attachment);
            return parts.join(' | ');
        };

        const formatLogDetail = (l) => {
            if (!l.detail) return '';
            if (typeof l.detail === 'string') return l.detail;
            try {
                return JSON.stringify(l.detail, null, 2);
            } catch (e) {
                return String(l.detail);
            }
        };

        const formatMoney = (value) => {
            const n = Number(value || 0);
            return Number.isFinite(n) ? n.toFixed(0) : '0';
        };

        const HOLDER_NAME_PATTERN = /^[A-Za-z][A-Za-z .,'()&@/_\-+]*$/;

        const validateIcHolderName = (value) => {
            const holderName = String(value || '').trim();
            if (!holderName) return '请输入持卡人姓名';
            if (holderName.length > 24) return '持卡人姓名不能超过 24 个字符';
            if (!HOLDER_NAME_PATTERN.test(holderName)) return '持卡人姓名仅支持英文与常用符号';
            return '';
        };

        const icStatusInfo = (status) => {
            const map = {
                pending_pickup: { text: '待领卡', className: 'badge-warning' },
                active: { text: '正常', className: 'badge-success' },
                disabled: { text: '停用', className: 'badge-danger' },
                lost: { text: '挂失', className: 'badge-danger' },
                refunded: { text: '已退卡', className: 'badge-secondary' }
            };
            return map[String(status || '').toLowerCase()] || { text: status || '未知', className: 'badge-secondary' };
        };

        const icStatusColor = (status) => {
            const s = String(status || '').toLowerCase();
            if (s === 'active') return 'var(--success)';
            if (s === 'pending_pickup') return 'var(--warning)';
            return 'var(--danger)';
        };

        const cardOrderCode = (card) => {
            const resolved = String(
                card?.order_code ||
                card?.voucher_code ||
                card?.code ||
                card?.orderCode ||
                card?.card_server_data?.order_code ||
                card?.card_server_data?.voucher_code ||
                ''
            ).trim();
            if (resolved) return resolved;
            return String(card?.source || '').trim() ? '现场办卡' : '---';
        };

        const displayIcCardId = (card) => {
            return String(card?.display_card_id || card?.card_id || '---').trim() || '---';
        };

        const icEventTitle = (event) => {
            const map = {
                create: '后台建卡',
                update: '信息更新',
                topup: '余额充值',
                open: '现场办卡',
                redeem: '线上兑卡',
                order_created: '线上购卡',
                check: '进出站检查',
                activated: '正式启用',
                delete: '删除卡片'
            };
            return map[String(event?.type || '').toLowerCase()] || (event?.type || '事件');
        };

        const formatIcEventDetail = (event) => {
            const detail = event?.detail != null ? event.detail : event;
            if (typeof detail === 'string') return detail;
            try {
                return JSON.stringify(detail || {}, null, 2);
            } catch (_) {
                return String(detail || '');
            }
        };
        
        const getStationName = (code) => {
            const s = stations.value.find(x => x.code === code);
            return s ? (s.name || s.cn_name) : code;
        };

        const getStationInfo = (code) => {
            return stations.value.find(x => x.code === code) || { code, name: code, en_name: '' };
        };

        const fetchAssetsManifest = async () => {
            const data = await requestJson('/api/assets/manifest');
            assetsManifest.routeMap = data ? (data.routeMap || null) : null;
            assetsManifest.fareTable = data ? (data.fareTable || null) : null;
            assetsManifest.updatedAt = data ? (data.updatedAt || null) : null;
            assetsRouteMapUrl.value = buildAssetUrl(assetsManifest.routeMap);
            assetsFareTableUrl.value = buildAssetUrl(assetsManifest.fareTable);

            assetsFarePreview.headers = [];
            assetsFarePreview.rows = [];

            if (assetsManifest.fareTable) {
                try {
                    const r = await fetch(assetsFareTableUrl.value);
                    const text = await r.text();
                    const name = String(assetsManifest.fareTable || '').toLowerCase();
                    if (name.endsWith('.json')) {
                        const obj = parseJsonSafe(text);
                        if (Array.isArray(obj)) {
                            const headers = [];
                            for (const it of obj) {
                                if (it && typeof it === 'object' && !Array.isArray(it)) {
                                    for (const k of Object.keys(it)) if (!headers.includes(k)) headers.push(k);
                                }
                            }
                            assetsFarePreview.headers = headers;
                            assetsFarePreview.rows = obj.filter(x => x && typeof x === 'object' && !Array.isArray(x));
                        } else if (obj && typeof obj === 'object' && Array.isArray(obj.rows) && Array.isArray(obj.headers)) {
                            assetsFarePreview.headers = obj.headers;
                            const rows = [];
                            for (const row of obj.rows) {
                                const r2 = {};
                                for (let i = 0; i < obj.headers.length; i++) r2[obj.headers[i]] = row[i];
                                rows.push(r2);
                            }
                            assetsFarePreview.rows = rows;
                        }
                    } else if (name.endsWith('.csv')) {
                        const parsed = parseCsvSimple(text);
                        assetsFarePreview.headers = parsed.headers;
                        assetsFarePreview.rows = parsed.rows;
                    }
                } catch (e) {}
            }
        };

        const uploadAssetFile = async (url, file) => {
            const fd = new FormData();
            fd.append('file', file);
            await requestJson(url, { method: 'POST', body: fd }, { expectOk: true });
            await fetchAssetsManifest();
        };

        const uploadRouteMap = async (ev) => {
            const f = ev && ev.target && ev.target.files && ev.target.files[0];
            if (!f) return;
            ev.target.value = '';
            await uploadAssetFile('/api/assets/route-map', f);
        };

        const uploadFareTable = async (ev) => {
            const f = ev && ev.target && ev.target.files && ev.target.files[0];
            if (!f) return;
            ev.target.value = '';
            await uploadAssetFile('/api/assets/fare-table', f);
        };

        const deleteRouteMap = async () => {
            await requestJson('/api/assets/route-map', { method: 'DELETE' }, { expectOk: true });
            await fetchAssetsManifest();
        };

        const deleteFareTable = async () => {
            await requestJson('/api/assets/fare-table', { method: 'DELETE' }, { expectOk: true });
            await fetchAssetsManifest();
        };

        const transferIndex = computed(() => {
            const out = new Map();
            const inn = new Map();

            for (const s of (stations.value || [])) {
                const from = String(s?.code || '').trim();
                if (!from) continue;
                if (!s?.transfer_enabled) continue;
                const list = Array.isArray(s.transfer_to) ? s.transfer_to : [];
                for (const t of list) {
                    const to = String((t && typeof t === 'object') ? (t.code || t.station || t.id || '') : t).trim();
                    if (!to || to === from) continue;
                    const o = out.get(from) || [];
                    o.push(to);
                    out.set(from, o);
                    const i = inn.get(to) || [];
                    i.push(from);
                    inn.set(to, i);
                }
            }

            const uniq = (arr) => Array.from(new Set((arr || []).filter(Boolean)));
            const getOut = (code) => uniq(out.get(code));
            const getIn = (code) => uniq(inn.get(code));

            return { getOut, getIn };
        });

        const stationLinesIndex = computed(() => {
            const map = new Map();
            for (const li of (lines.value || [])) {
                const id = String(li?.id || '').trim();
                const color = li?.color || '#93a2b7';
                if (!id) continue;
                const arr = Array.isArray(li.stations) ? li.stations : (Array.isArray(li.stops) ? li.stops : []);
                for (const sc of arr) {
                    const code = String(sc || '').trim();
                    if (!code) continue;
                    const cur = map.get(code) || [];
                    cur.push({ id, color });
                    map.set(code, cur);
                }
            }
            const uniqById = (arr) => {
                const seen = new Set();
                const out = [];
                for (const x of (arr || [])) {
                    if (!x?.id || seen.has(x.id)) continue;
                    seen.add(x.id);
                    out.push({ id: x.id, color: x.color || '#93a2b7' });
                }
                return out;
            };
            return {
                getLines: (code) => uniqById(map.get(String(code || '').trim()) || [])
            };
        });

        const isTransferStation = (code) => {
            const c = String(code || '').trim();
            if (!c) return false;
            const { getOut, getIn } = transferIndex.value;
            return (getOut(c).length + getIn(c).length) > 0;
        };

        const getTransferLineBadges = (code) => {
            const c = String(code || '').trim();
            if (!c) return [];
            const { getOut, getIn } = transferIndex.value;
            const partners = [...getOut(c), ...getIn(c)];
            const lineId = selectedLine.value?.id;
            const badges = [];
            const seen = new Set();
            for (const p of partners) {
                for (const li of stationLinesIndex.value.getLines(p)) {
                    if (lineId && li.id === lineId) continue;
                    if (seen.has(li.id)) continue;
                    seen.add(li.id);
                    badges.push(li);
                }
            }
            return badges.slice(0, 6);
        };

        const getTransferTitleSuffix = (code) => {
            const c = String(code || '').trim();
            if (!c) return '';
            const { getOut, getIn } = transferIndex.value;
            const out = getOut(c);
            const inn = getIn(c);
            if (out.length === 0 && inn.length === 0) return '';
            const fmt = (arr) => arr.map(x => `${getStationName(x)} (${x})`).join(', ');
            let s = '\nXFER';
            if (out.length > 0) s += `\nTo: ${fmt(out)}`;
            if (inn.length > 0) s += `\nFrom: ${fmt(inn)}`;
            return s;
        };

        const viewTicketDetails = async (ticket) => {
            selectedTicket.value = null;
            showTicketModal.value = true;
            try {
                const res = await requestJson(`/api/tickets/${encodeURIComponent(ticket.ticket_id)}`);
                if (res && res.ok) selectedTicket.value = res;
            } catch (e) {
                console.error(e);
            }
        };

        const closeTicketModal = () => {
            showTicketModal.value = false;
            selectedTicket.value = null;
        };

        // --- Drag & Drop Logic ---
        const onStationDragStart = (index) => {
            if (fareMode.value) return; // Disable drag in fare mode
            draggingStationIndex.value = index;
        };

        const onStationDragOver = (index) => {
             // Only swap if we are actually dragging
             if (draggingStationIndex.value === null) return;
             if (draggingStationIndex.value === index) return;
             
             // Swap in local array for visual feedback
             const list = selectedLine.value.stations;
             const temp = list[draggingStationIndex.value];
             list.splice(draggingStationIndex.value, 1);
             list.splice(index, 0, temp);
             draggingStationIndex.value = index;
        };

        const onStationDrop = async () => {
            if (draggingStationIndex.value === null) return;
            try {
                await updateLineStations(selectedLine.value.id, selectedLine.value.stations);
            } catch (e) {
                alert(`保存站序失败：${e?.message || String(e)}`);
                await fetchData();
            }
            draggingStationIndex.value = null;
        };

        // --- Order Management ---
        const fetchOrders = async () => {
            try {
                const res = await requestJson('/api/orders');
                if (res && res.ok) orders.value = res.orders;
            } catch (e) { console.error(e); }
        };

        const fetchIcCards = async (keepSelection = true) => {
            const sp = new URLSearchParams();
            if (icCardSearch.value.trim()) sp.set('q', icCardSearch.value.trim());
            const res = await requestJson(`/api/ic-cards?${sp.toString()}`);
            icCards.value = res?.cards || [];
            if (keepSelection && icSelectedId.value && icCards.value.some((card) => card.card_id === icSelectedId.value)) {
                await loadIcCard(icSelectedId.value);
            } else if (icSelectedId.value && !icCards.value.some((card) => card.card_id === icSelectedId.value)) {
                icSelectedId.value = '';
                icSelectedCard.value = null;
                icSelectedEvents.value = [];
            }
        };

        const loadIcCard = async (id) => {
            const res = await requestJson(`/api/ic-cards/${encodeURIComponent(id)}`);
            const card = res?.card || null;
            icSelectedId.value = id;
            icSelectedCard.value = card;
            icSelectedEvents.value = res?.events || [];
            icDetailForm.holder_name = card?.holder_name || '';
            icDetailForm.status = card?.status || 'active';
        };

        const syncSelectedIcCard = async () => {
            if (!icSelectedId.value) return;
            const res = await requestJson(`/api/ic-cards/${encodeURIComponent(icSelectedId.value)}`);
            const card = res?.card || null;
            if (!card) return;
            icSelectedCard.value = card;
            icSelectedEvents.value = res?.events || [];
        };

        const stopIcCardSync = () => {
            if (icCardSyncTimer) {
                clearInterval(icCardSyncTimer);
                icCardSyncTimer = null;
            }
        };

        const startIcCardSync = () => {
            stopIcCardSync();
            if (currentView.value !== 'iccards') return;
            icCardSyncTimer = setInterval(() => {
                fetchIcCards(false).catch(console.error);
                syncSelectedIcCard().catch(console.error);
            }, 3000);
        };

        const createIcCard = async () => {
            const holder_name = String(icCreateForm.holder_name || '').trim();
            const balance = Number(icCreateForm.balance || 0) || 0;
            const holderError = validateIcHolderName(holder_name);
            if (holderError) return alert(holderError);
            await runMutation(async () => {
                const res = await requestJson('/api/ic-cards', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ holder_name, balance })
                }, { expectOk: true });
                icCreateForm.holder_name = '';
                icCreateForm.balance = 50;
                await fetchIcCards(false);
                if (res?.card_id) await loadIcCard(res.card_id);
            }, { successMessage: 'IC 卡已创建' });
        };

        const saveIcCard = async () => {
            if (!icSelectedId.value) return alert('请先选择一张 IC 卡');
            const payload = {
                holder_name: String(icDetailForm.holder_name || '').trim(),
                status: icDetailForm.status || 'active'
            };
            const holderError = validateIcHolderName(payload.holder_name);
            if (holderError) return alert(holderError);
            await runMutation(async () => {
                await requestJson(`/api/ic-cards/${encodeURIComponent(icSelectedId.value)}`, {
                    method: 'PUT',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify(payload)
                }, { expectOk: true });
                await fetchIcCards(false);
                await loadIcCard(icSelectedId.value);
            }, { successMessage: '已保存 IC 卡信息' });
        };

        const topupIcCard = async () => {
            if (!icSelectedId.value) return alert('请先选择一张 IC 卡');
            const raw = await appDialog.prompt({
                title: 'IC 卡充值',
                message: `请输入给 ${icSelectedId.value} 充值的金额`,
                defaultValue: '50',
                placeholder: '请输入充值金额',
                confirmText: '确认充值'
            });
            if (raw == null) return;
            const amount = Number(raw);
            if (!(amount > 0)) return alert('充值金额必须大于 0');
            await runMutation(async () => {
                await requestJson(`/api/ic-cards/${encodeURIComponent(icSelectedId.value)}/topup`, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ amount })
                }, { expectOk: true });
                await fetchIcCards(false);
                await loadIcCard(icSelectedId.value);
            }, { successMessage: '充值成功' });
        };

        const deleteIcCard = async () => {
            if (!icSelectedId.value || !icSelectedCard.value) return alert('请先选择一张 IC 卡');
            if (!await appDialog.confirm({
                title: '删除 IC 卡',
                message: `确定删除 IC 卡 ${icSelectedId.value} 吗？此操作不可撤销。`,
                confirmText: '确认删除'
            })) return;
            const removingId = icSelectedId.value;
            await runMutation(async () => {
                await requestJson(`/api/ic-cards/${encodeURIComponent(removingId)}`, { method: 'DELETE' }, { expectOk: true });
                icSelectedId.value = '';
                icSelectedCard.value = null;
                icSelectedEvents.value = [];
                await fetchIcCards(false);
            }, { successMessage: 'IC 卡已删除' });
        };

        const buildLogsUrl = () => {
            const sp = new URLSearchParams();
            sp.set('max', String(logMax.value || 200));
            if (logCategory.value) sp.set('category', logCategory.value);
            if (logTypeFilter.value) sp.set('type', logTypeFilter.value);
            if (logQuery.value) sp.set('q', logQuery.value);
            return `/api/logs?${sp.toString()}`;
        };

        const fetchLogs = async () => {
            logLoading.value = true;
            try {
                const res = await requestJson(buildLogsUrl());
                if (res && res.ok) logs.value = res.logs || [];
            } catch (e) {
                console.error(e);
            } finally {
                logLoading.value = false;
            }
        };

        const deleteOrder = async (code) => {
            if (!await appDialog.confirm({
                title: '删除凭证',
                message: `确定删除凭证 ${code} 吗？`,
                confirmText: '确认删除'
            })) return;
            await runMutation(async () => {
                await requestJson(`/api/orders/${encodeURIComponent(code)}`, { method: 'DELETE' }, { expectOk: true });
                await fetchOrders();
            });
        };

        const updateLineInfo = async () => {
            if (!selectedLine.value) return;
            await runMutation(async () => {
                await requestJson(`/api/lines/${encodeURIComponent(selectedLine.value.id)}`, {
                    method: 'PUT',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify(selectedLine.value)
                }, { expectOk: true });
            });
        };

        const getFareText = (idx) => {
            if (!selectedLine.value || !selectedLine.value.stations) return '';
            const s1 = selectedLine.value.stations[idx];
            const s2 = selectedLine.value.stations[idx + 1];
            if (!s1 || !s2) return '';
            
            const f = fares.value.find(x => (x.from === s1 && x.to === s2) || (x.from === s2 && x.to === s1));
            if (!f) return '';
            
            const reg = f.cost_regular ?? f.cost ?? 0;
            const exp = f.cost_express ?? f.cost ?? 0;
            return `¥${reg} / ¥${exp}`;
        };

        const fetchData = async () => {
            try {
                const safeFetch = (url, defaultVal) => requestJson(url).catch(e => {
                    console.error(`Fetch failed for ${url}`, e);
                    lastActionError.value = e?.message || String(e);
                    return defaultVal;
                });
                
                const safeFetchList = (url, key) => requestJson(url).then(d => d?.[key] || []).catch(e => {
                    console.error(`Fetch list failed for ${url}`, e);
                    lastActionError.value = e?.message || String(e);
                    return [];
                });

                const [s, l, f, c, t, lg, st, ord, cards] = await Promise.all([
                    safeFetch('/api/stations', []),
                    safeFetch('/api/lines', []),
                    safeFetch('/api/fares', []),
                    safeFetch('/api/config', {}),
                    safeFetchList('/api/tickets', 'tickets'),
                    safeFetchList(buildLogsUrl(), 'logs'),
                    safeFetch('/api/stats/ticket/total', {}).then(d => d.total || {}),
                    safeFetchList('/api/orders', 'orders'),
                    safeFetchList('/api/ic-cards', 'cards')
                ]);
                
                stations.value = s;
                lines.value = l;
                fares.value = f;
                Object.assign(config, c);
                tickets.value = t;
                logs.value = lg;
                Object.assign(stats, st);
                orders.value = ord;
                icCards.value = cards;
                
                // Refresh selected line if it exists
                if (selectedLine.value) {
                    const found = lines.value.find(l => l.id === selectedLine.value.id);
                    if (found) selectedLine.value = found;
                }
                if (icSelectedId.value && icCards.value.some((card) => card.card_id === icSelectedId.value)) {
                    await loadIcCard(icSelectedId.value);
                }

                loadFareMap();
            } catch (e) {
                console.error("Failed to fetch data", e);
            }
        };

        const loadFareMap = async () => {
            fareMapLoading.value = true;
            fareMapError.value = '';
            try {
                // Change to fetch the SVG text directly from the public API
                // Add timestamp to prevent caching
                const r = await fetch(`/api/public/fares/map/light?t=${Date.now()}`);
                const svg = await r.text();
                fareMapSvg.value = svg;
            } catch (e) {
                console.error("Failed to load fare map", e);
                fareMapError.value = '加载失败';
            } finally {
                fareMapLoading.value = false;
            }
        };

        const zoomFareMapIn = () => { fareMapScale.value = Math.min(3, Math.round((fareMapScale.value + 0.1) * 100) / 100); };
        const zoomFareMapOut = () => { fareMapScale.value = Math.max(0.3, Math.round((fareMapScale.value - 0.1) * 100) / 100); };
        const zoomFareMapReset = () => { fareMapScale.value = 1; };

        // --- Management Actions ---

        const createStation = async () => {
            if (!newStation.code || !newStation.name) return alert('请填写完整');
            await runMutation(async () => {
                await requestJson('/api/stations', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(newStation) }, { expectOk: true });
                showAddStation.value = false;
                Object.assign(newStation, { code: '', name: '', en_name: '' });
            });
        };
        
        const deleteStation = async (code) => {
            if (!await appDialog.confirm({
                title: '删除站点',
                message: '确定从库中删除该站点？这不会影响已存在于线路中的引用，但建议先从线路移除。',
                confirmText: '确认删除'
            })) return;
            await runMutation(async () => {
                await requestJson(`/api/stations/${encodeURIComponent(code)}`, { method: 'DELETE' }, { expectOk: true });
            });
        };

        const createLine = async () => {
            if (!newLine.id) return alert('请填写ID');
            await runMutation(async () => {
                await requestJson('/api/lines', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(newLine) }, { expectOk: true });
                showAddLine.value = false;
                Object.assign(newLine, { id: '', name: '', en_name: '', color: '#3366cc' });
            });
        };
        
        const deleteLine = async (id) => {
            if (!await appDialog.confirm({
                title: '删除线路',
                message: '确定删除此线路？',
                confirmText: '确认删除'
            })) return;
            await runMutation(async () => {
                await requestJson(`/api/lines/${encodeURIComponent(id)}`, { method: 'DELETE' }, { expectOk: true });
                if (selectedLine.value && selectedLine.value.id === id) selectedLine.value = null;
            });
        };

        // --- Visual Editor Logic ---

        const selectLine = (l) => {
            selectedLine.value = l;
            fareMode.value = false;
            fareSelection.value = [];
        };

        const availableStations = computed(() => {
            return stations.value;
        });

        const isStationInLine = (code) => {
            return selectedLine.value && (selectedLine.value.stations || []).includes(code);
        };

        const addStationToLine = async () => {
            if (!selectedLine.value) return;
            const { code, name, en_name } = newStation;
            
            if (!code || !name) return alert('请填写编号和名称');
            
            await runMutation(async () => {
                const existing = stations.value.find(s => s.code === code);
                if (!existing) {
                    await requestJson('/api/stations', { 
                        method: 'POST', 
                        headers: {'Content-Type': 'application/json'}, 
                        body: JSON.stringify({ code, name, en_name }) 
                    }, { expectOk: true });
                }

                if (isStationInLine(code)) throw new Error('该站点已在此线路中');

                const newStations = [...(selectedLine.value.stations || [])];
                newStations.push(code);
                await updateLineStations(selectedLine.value.id, newStations, { skipFetchData: true });
                Object.assign(newStation, { code: '', name: '', en_name: '' });
            });
        };

        const removeStationFromLine = async (code) => {
            if (!selectedLine.value) return;
            const newStations = selectedLine.value.stations.filter(s => s !== code);
            await updateLineStations(selectedLine.value.id, newStations);
        };

        const updateLineStations = async (lineId, stationsList, { skipFetchData } = {}) => {
            // Need to update the whole line object usually, or a specific endpoint
            // Assuming PUT /api/lines/:id updates fields provided
            const line = lines.value.find(l => l.id === lineId);
            if (!line) return;
            
            const updated = { ...line, stations: stationsList };
            
            const r = await requestJson(`/api/lines/${encodeURIComponent(lineId)}`, { 
                method: 'PUT', 
                headers: {'Content-Type': 'application/json'}, 
                body: JSON.stringify(updated) 
            }, { expectOk: true });
            if (!skipFetchData) await fetchData();
            return r;
        };

        const openStationModal = (code) => {
            const s = stations.value.find(x => x.code === code) || { code, name: code, en_name: '' };
            stationFormOriginalCode.value = s.code || code;
            stationForm.code = s.code || code;
            stationForm.name = s.name || s.cn_name || '';
            stationForm.en_name = s.en_name || s.enName || '';
            stationForm.transfer_enabled = !!s.transfer_enabled;
            stationForm.transfer_to = Array.isArray(s.transfer_to) ? [...s.transfer_to] : [];
            showStationModal.value = true;
        };

        const closeStationModal = () => {
            showStationModal.value = false;
        };

        const saveStationSettings = async () => {
            if (!stationFormOriginalCode.value) return;
            if (!stationForm.code) return alert('请填写站点编号');
            const oldCode = String(stationFormOriginalCode.value || '').trim();
            const newCode = String(stationForm.code || '').trim();
            if (!newCode) return alert('请填写站点编号');
            if (newCode !== oldCode) {
                if (!await appDialog.confirm({
                    title: '修改站点编号',
                    message: `确定将站点编号从 ${oldCode} 修改为 ${newCode} 吗？这会同步更新线路、票价、凭证等引用。`,
                    confirmText: '确认修改'
                })) return;
            }
            const payload = {
                code: newCode,
                name: stationForm.name,
                en_name: stationForm.en_name,
                transfer_enabled: !!stationForm.transfer_enabled,
                transfer_to: stationForm.transfer_enabled ? (Array.isArray(stationForm.transfer_to) ? stationForm.transfer_to : []) : []
            };
            await runMutation(async () => {
                await requestJson(`/api/stations/${encodeURIComponent(oldCode)}`, { method: 'PUT', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(payload) }, { expectOk: true });
                showStationModal.value = false;
            });
        };

        const transferTargets = computed(() => {
            const fromCode = stationForm.code;
            return stations.value
                .filter(s => s && s.code && s.code !== fromCode)
                .map(s => ({ code: s.code, name: s.name || s.cn_name || s.code, en_name: s.en_name || s.enName || '' }));
        });

        const openLineModal = () => {
            if (!selectedLine.value) return;
            lineFormOriginalId.value = selectedLine.value.id;
            lineForm.id = selectedLine.value.id || '';
            lineForm.name = selectedLine.value.name || '';
            lineForm.en_name = selectedLine.value.en_name || '';
            lineForm.color = selectedLine.value.color || '#3366cc';
            lineForm.stations = Array.isArray(selectedLine.value.stations) ? [...selectedLine.value.stations] : [];
            showLineModal.value = true;
        };

        const closeLineModal = () => {
            showLineModal.value = false;
        };

        const saveLineSettings = async () => {
            if (!lineFormOriginalId.value) return;
            const oldId = String(lineFormOriginalId.value || '').trim();
            const newId = String(lineForm.id || '').trim();
            if (!newId) return alert('请填写线路编号');
            if (newId !== oldId) {
                if (!await appDialog.confirm({
                    title: '修改线路编号',
                    message: `确定将线路编号从 ${oldId} 修改为 ${newId} 吗？`,
                    confirmText: '确认修改'
                })) return;
            }
            const payload = {
                id: newId,
                name: lineForm.name,
                en_name: lineForm.en_name,
                color: lineForm.color,
                stations: Array.isArray(lineForm.stations) ? [...lineForm.stations] : []
            };
            await runMutation(async () => {
                await requestJson(`/api/lines/${encodeURIComponent(oldId)}`, { method: 'PUT', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(payload) }, { expectOk: true });
                showLineModal.value = false;
                const next = lines.value.find(l => l.id === newId);
                if (next) selectLine(next);
            });
        };

        const handleStationClick = async (code) => {
            if (stationEditMode.value) {
                openStationModal(code);
                return;
            }
            if (fareMode.value) {
                // Toggle selection
                const idx = fareSelection.value.indexOf(code);
                if (idx >= 0) {
                    fareSelection.value.splice(idx, 1);
                } else {
                    if (fareSelection.value.length < 2) {
                        fareSelection.value.push(code);
                    } else {
                        // Replace the second one or reset? Let's shift
                        fareSelection.value.shift();
                        fareSelection.value.push(code);
                    }
                }

                // If we have 2, check fare
                if (fareSelection.value.length === 2) {
                    checkAndOpenFareModal();
                }
            } else {
                // Normal mode: Ask to delete?
                if (await appDialog.confirm({
                    title: '移除站点',
                    message: `从线路 ${selectedLine.value.id} 中移除站点 ${getStationName(code)}？`,
                    confirmText: '确认移除'
                })) {
                    await removeStationFromLine(code);
                }
            }
        };

        watch(fareMode, (v) => {
            if (v) stationEditMode.value = false;
        });
        watch(stationEditMode, (v) => {
            if (v) {
                fareMode.value = false;
                fareSelection.value = [];
            }
        });
        watch(icCardSearch, () => {
            window.clearTimeout(icCardSearch._timer);
            icCardSearch._timer = window.setTimeout(() => {
                fetchIcCards(false).catch(console.error);
            }, 240);
        });

        const isStationSelected = (code) => {
            return fareSelection.value.includes(code);
        };

        const checkAndOpenFareModal = () => {
            const [from, to] = fareSelection.value;
            // Find existing fare
            // Fare direction is usually bidirectional or defined one way. Let's check both.
            let f = fares.value.find(x => (x.from === from && x.to === to) || (x.from === to && x.to === from));
            
            if (f) {
                currentFare.exists = true;
                currentFare.cost_regular = f.cost_regular || f.cost || 0;
                currentFare.cost_express = f.cost_express || f.cost || 0;
            } else {
                currentFare.exists = false;
                currentFare.cost_regular = 0;
                currentFare.cost_express = 0;
            }
            showFareModal.value = true;
        };

        const closeFareModal = () => {
            showFareModal.value = false;
            fareSelection.value = [];
        };

        const saveCurrentFare = async () => {
            const [from, to] = fareSelection.value;
            await runMutation(async () => {
                if (selectedLine.value) {
                    const stations = selectedLine.value.stations || [];
                    const idx1 = stations.indexOf(from);
                    const idx2 = stations.indexOf(to);
                    if (idx1 !== -1 && idx2 !== -1) {
                        const start = Math.min(idx1, idx2);
                        const end = Math.max(idx1, idx2);
                        if (end - start > 1) {
                            if (!await appDialog.confirm({
                                title: '区间票价应用',
                                message: `检测到所选站点间有 ${end - start - 1} 个中间站，是否将此票价应用到该区间内的每一段？`,
                                confirmText: '应用到整段',
                                cancelText: '仅保存当前区间'
                            })) {
                                await submitFare(from, to);
                            } else {
                                for (let k = start; k < end; k++) {
                                    await submitFare(stations[k], stations[k+1]);
                                }
                            }
                        } else {
                            await submitFare(from, to);
                        }
                    }
                }
                closeFareModal();
            });
        };

        const submitFare = async (from, to) => {
            const payload = {
                from, to,
                cost_regular: currentFare.cost_regular,
                cost_express: currentFare.cost_express
            };
            await requestJson('/api/fares', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(payload) }, { expectOk: true });
        };

        const deleteCurrentFare = async () => {
            const [from, to] = fareSelection.value;
            await runMutation(async () => {
                await requestJson('/api/fares', { method: 'DELETE', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ from, to }) }, { expectOk: true });
                closeFareModal();
            });
        };

        const saveConfig = async () => {
            await runMutation(async () => {
                await requestJson('/api/config', { method: 'PUT', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(config) }, { expectOk: true });
            }, { successMessage: '保存成功' });
        };

        const exportData = () => {
            window.open('/api/export', '_blank');
        };

        // Socket Listeners
        socket.on('connect', () => { connected.value = true; });
        socket.on('disconnect', () => { connected.value = false; });
        
        socket.on('stations:updated', (data) => { 
            stations.value = data; 
            // Refresh map when stations change
            loadFareMap();
        });

        socket.on('lines:updated', (data) => { 
            lines.value = data; 
            // Update selectedLine reference if it exists
            if (selectedLine.value) {
                const updated = data.find(l => l.id === selectedLine.value.id);
                if (updated) {
                    selectedLine.value = updated;
                } else {
                    selectedLine.value = null; // Line was deleted
                }
            }
            loadFareMap();
        });

        socket.on('fares:updated', (data) => { 
            fares.value = data;
            loadFareMap();
        });
        socket.on('config:updated', (data) => { Object.assign(config, data); loadFareMap(); });
        
        socket.on('stats:ticket:updated', (item) => {
            stats.sold_tickets += item.sold_tickets;
            stats.revenue += item.revenue;
        });
        socket.on('ic-card:created', () => { fetchIcCards(false).catch(console.error); });
        socket.on('ic-card:opened', () => { fetchIcCards(false).catch(console.error); });
        socket.on('ic-card:check', () => {
            fetchIcCards(false).catch(console.error);
            syncSelectedIcCard().catch(console.error);
        });
        socket.on('ic-card:topup', () => {
            fetchIcCards(false).catch(console.error);
            syncSelectedIcCard().catch(console.error);
        });
        socket.on('ic-card:sync', () => {
            fetchIcCards(false).catch(console.error);
            syncSelectedIcCard().catch(console.error);
        });

        watch(currentView, (v) => {
            sidebarOpen.value = false;
            if (v === 'assets') fetchAssetsManifest();
            if (v === 'logs') fetchLogs();
            if (v === 'iccards') {
                fetchIcCards(true).catch(console.error);
                syncSelectedIcCard().catch(() => {});
                startIcCardSync();
            } else {
                stopIcCardSync();
            }
            const sp = new URLSearchParams(location.search);
            if (v === 'dashboard') sp.delete('view');
            else sp.set('view', v);
            const q = sp.toString();
            const target = `${location.pathname}${q ? `?${q}` : ''}`;
            history.replaceState(null, '', target);
        });
        
        // Initial Load
        onMounted(() => {
            fetchData();
            fetchAssetsManifest();
            if (currentView.value === 'iccards') {
                fetchIcCards(true).catch(console.error);
                startIcCardSync();
            }
            window.addEventListener('mouseup', async () => {
                if (draggingStationIndex.value !== null) {
                    if (selectedLine.value) {
                        try {
                            await requestJson(`/api/lines/${encodeURIComponent(selectedLine.value.id)}`, {
                                method: 'PUT',
                                headers: {'Content-Type': 'application/json'},
                                body: JSON.stringify({ ...(lines.value.find(l => l.id === selectedLine.value.id) || selectedLine.value), stations: selectedLine.value.stations })
                            }, { expectOk: true });
                            await fetchData();
                        } catch (e) {
                            alert(`保存站序失败：${e?.message || String(e)}`);
                            await fetchData();
                        }
                    }
                    draggingStationIndex.value = null;
                }
            });
        });
        
        onUnmounted(() => {
            stopIcCardSync();
        });

        // Computed
        const recentLogs = computed(() => logs.value);
        const orderList = computed(() => orders.value);
        const icCardStats = computed(() => ({
            total: icCards.value.length,
            pending: icCards.value.filter((card) => card.status === 'pending_pickup').length,
            active: icCards.value.filter((card) => card.status === 'active').length,
            balance: icCards.value.reduce((sum, card) => sum + (Number(card.balance || 0) || 0), 0)
        }));
        const ticketList = computed(() => {
            if (!ticketSearch.value) return tickets.value.slice(0, 50);
            const q = ticketSearch.value.toLowerCase();
            return tickets.value.filter(t => 
                t.ticket_id.toLowerCase().includes(q) || 
                (t.start && t.start.toLowerCase().includes(q)) || 
                (t.terminal && t.terminal.toLowerCase().includes(q))
            ).slice(0, 50);
        });

        const exportFareMap = () => {
            const svgData = fareMapSvg.value;
            if (!svgData) return alert('地图尚未加载');

            const canvas = document.createElement('canvas');
            const ctx = canvas.getContext('2d');
            const img = new Image();
            
            // Extract width/height from SVG string or default
            const matchW = svgData.match(/width="([\d.]+)"/);
            const matchH = svgData.match(/height="([\d.]+)"/);
            const w = matchW ? Number(matchW[1]) : 1000;
            const h = matchH ? Number(matchH[1]) : 1000;
            const scale = 3;
            canvas.width = Math.max(1, Math.round(w * scale));
            canvas.height = Math.max(1, Math.round(h * scale));

            const blob = new Blob([svgData], {type: 'image/svg+xml;charset=utf-8'});
            const url = URL.createObjectURL(blob);
            
            img.onload = () => {
                ctx.setTransform(scale, 0, 0, scale, 0, 0);
                ctx.fillStyle = '#ffffff';
                ctx.fillRect(0, 0, w, h);
                ctx.drawImage(img, 0, 0);
                URL.revokeObjectURL(url);
                
                const pngUrl = canvas.toDataURL('image/png');
                const a = document.createElement('a');
                a.href = pngUrl;
                a.download = 'fare-map.png';
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
            };
            img.src = url;
        };

        return {
            currentView, viewTitle, connected, sidebarOpen,
            stations, lines, fares, stats, config, recentLogs, ticketList,
            logs, logCategory, logTypeFilter, logQuery, logMax, logLoading, fetchLogs,
            orders, orderList, fetchOrders, deleteOrder,
            icCards, icCardSearch, icSelectedId, icSelectedCard, icSelectedEvents, icCreateForm, icDetailForm, icCardStats,
            createIcCard, loadIcCard, saveIcCard, topupIcCard, deleteIcCard, icStatusInfo, icStatusColor, icEventTitle, formatIcEventDetail, cardOrderCode, displayIcCardId, formatMoney,
            showAddLine, showAddStation, newLine, newStation, fareMapSvg, ticketSearch,
            assetsManifest, assetsFarePreview, assetsRouteMapUrl, assetsFareTableUrl,
            uploadRouteMap, uploadFareTable, deleteRouteMap, deleteFareTable,
            
            // Management
            selectedLine, fareMode, stationEditMode, fareSelection, showFareModal, currentFare, availableStations,
            selectLine, createLine, deleteLine, deleteStation, updateLineInfo, getFareText,
            isStationInLine, addStationToLine, removeStationFromLine,
            handleStationClick, isStationSelected,
            onStationDragStart, onStationDragOver, onStationDrop, draggingStationIndex,
            showStationModal, stationForm, stationFormOriginalCode, transferTargets, saveStationSettings, closeStationModal,
            showLineModal, lineForm, openLineModal, saveLineSettings, closeLineModal,
            
            // Tickets
            showTicketModal, selectedTicket, viewTicketDetails, closeTicketModal, formatTicketStatus, formatTicketEvent, formatTicketEventLocation, formatTicketEventExtra, formatLogType, formatTrainType,
            
            saveCurrentFare, deleteCurrentFare, closeFareModal,

            saveConfig, exportData, exportFareMap,
            formatTime, formatLogDetail, getStationName, getStationInfo, loadFareMap, fetchData, refreshData: fetchData,
            fareMapScale, fareMapLoading, fareMapError, zoomFareMapIn, zoomFareMapOut, zoomFareMapReset,
            isTransferStation, getTransferTitleSuffix, getTransferLineBadges
        };
    }
}).mount('#app');
