/**
 * <folk-cred-dashboard> — CredRank contribution recognition dashboard.
 *
 * Leaderboard, per-contributor breakdown, and admin weight config.
 * Fetches from /:space/rcred/api/* endpoints.
 */

import { authFetch } from '../../../shared/auth-fetch';

interface LeaderboardEntry {
	rank: number;
	did: string;
	label: string;
	cred: number;
	grainLifetime: number;
	topModule: string;
	lastActive: number;
}

interface ScoresResponse {
	scores: LeaderboardEntry[];
	totalCred: number;
	computedAt: number;
	epochId: string;
}

interface ContributorDetail {
	did: string;
	label: string;
	cred: number;
	rawScore: number;
	grainLifetime: number;
	breakdown: Record<string, number>;
	epochScores: Record<string, number>;
	lastActive: number;
}

interface WeightConfig {
	type: string;
	weight: number;
	description: string;
}

interface ConfigResponse {
	weights: Record<string, WeightConfig>;
	grainPerEpoch: number;
	epochLengthDays: number;
	slowFraction: number;
	fastFraction: number;
	dampingFactor: number;
	lookbackDays: number;
	enabled: boolean;
	lastEpochAt: number;
}

const MODULE_COLORS: Record<string, string> = {
	rtasks: '#3b82f6',
	rdocs: '#8b5cf6',
	rchats: '#06b6d4',
	rcal: '#f59e0b',
	rvote: '#ec4899',
	rflows: '#22c55e',
	rtime: '#a78bfa',
	rwallet: '#d97706',
};

const MODULE_LABELS: Record<string, string> = {
	rtasks: 'Tasks',
	rdocs: 'Docs',
	rchats: 'Chats',
	rcal: 'Calendar',
	rvote: 'Voting',
	rflows: 'Flows',
	rtime: 'Time',
	rwallet: 'Wallet',
};

class FolkCredDashboard extends HTMLElement {
	private shadow: ShadowRoot;
	private space = '';
	private view: 'leaderboard' | 'contributor' | 'config' = 'leaderboard';
	private scores: LeaderboardEntry[] = [];
	private totalCred = 0;
	private computedAt = 0;
	private selectedContributor: ContributorDetail | null = null;
	private config: ConfigResponse | null = null;
	private loading = false;
	private recomputing = false;
	private error = '';

	constructor() {
		super();
		this.shadow = this.attachShadow({ mode: 'open' });
	}

	connectedCallback() {
		this.space = this.getAttribute('space') || 'demo';
		this.loadScores();
	}

	private async loadScores() {
		this.loading = true;
		this.render();
		try {
			const res = await authFetch(`/${this.space}/rcred/api/scores?space=${this.space}`);
			const data: ScoresResponse = await res.json();
			this.scores = data.scores || [];
			this.totalCred = data.totalCred || 0;
			this.computedAt = data.computedAt || 0;
			this.error = '';
		} catch (e) {
			this.error = 'Failed to load scores';
		}
		this.loading = false;
		this.render();
	}

	private async loadContributor(did: string) {
		this.loading = true;
		this.render();
		try {
			const res = await authFetch(`/${this.space}/rcred/api/scores/${encodeURIComponent(did)}?space=${this.space}`);
			if (!res.ok) throw new Error('Not found');
			this.selectedContributor = await res.json();
			this.view = 'contributor';
			this.error = '';
		} catch (e) {
			this.error = 'Contributor not found';
		}
		this.loading = false;
		this.render();
	}

	private async loadConfig() {
		this.loading = true;
		this.render();
		try {
			const res = await authFetch(`/${this.space}/rcred/api/config?space=${this.space}`);
			this.config = await res.json();
			this.view = 'config';
			this.error = '';
		} catch (e) {
			this.error = 'Failed to load config';
		}
		this.loading = false;
		this.render();
	}

	private async triggerRecompute() {
		this.recomputing = true;
		this.render();
		try {
			const res = await authFetch(`/${this.space}/rcred/api/recompute?space=${this.space}`, { method: 'POST' });
			const result = await res.json();
			if (result.success) {
				await this.loadScores();
			} else {
				this.error = result.error || 'Recompute failed';
			}
		} catch (e) {
			this.error = 'Recompute failed';
		}
		this.recomputing = false;
		this.render();
	}

	private async saveWeights() {
		if (!this.config) return;
		try {
			await authFetch(`/${this.space}/rcred/api/config?space=${this.space}`, {
				method: 'PUT',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ weights: this.config.weights }),
			});
			this.error = '';
		} catch (e) {
			this.error = 'Failed to save config';
		}
		this.render();
	}

	private render() {
		const timeAgo = (ts: number) => {
			if (!ts) return 'never';
			const diff = Date.now() - ts;
			if (diff < 60_000) return 'just now';
			if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`;
			if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)}h ago`;
			return `${Math.floor(diff / 86_400_000)}d ago`;
		};

		let content = '';

		if (this.view === 'leaderboard') {
			content = this.renderLeaderboard(timeAgo);
		} else if (this.view === 'contributor') {
			content = this.renderContributor(timeAgo);
		} else if (this.view === 'config') {
			content = this.renderConfig();
		}

		this.shadow.innerHTML = `
<style>
:host { display:block; font-family:system-ui,-apple-system,sans-serif; color:#e2e8f0; }
* { box-sizing:border-box; margin:0; padding:0; }

.container { max-width:900px; margin:0 auto; padding:1.5rem; }

.header { display:flex; align-items:center; justify-content:space-between; margin-bottom:1.5rem; flex-wrap:wrap; gap:0.75rem; }
.header h1 { font-size:1.5rem; display:flex; align-items:center; gap:0.5rem; }
.header-actions { display:flex; gap:0.5rem; align-items:center; }

.meta { font-size:0.75rem; color:#64748b; margin-bottom:1rem; }

button { background:#1e293b; color:#e2e8f0; border:1px solid #334155; border-radius:0.375rem;
  padding:0.4rem 0.75rem; font-size:0.8rem; cursor:pointer; transition:background 0.15s; }
button:hover { background:#334155; }
button.primary { background:linear-gradient(to right,#f59e0b,#d97706); border:none; color:#000; font-weight:600; }
button.primary:hover { opacity:0.9; }
button:disabled { opacity:0.5; cursor:not-allowed; }

.tab-bar { display:flex; gap:0.25rem; margin-bottom:1rem; }
.tab { padding:0.4rem 0.75rem; border-radius:0.375rem; cursor:pointer; font-size:0.8rem;
  background:transparent; border:1px solid transparent; color:#94a3b8; }
.tab:hover { background:#1e293b; }
.tab.active { background:#1e293b; color:#fbbf24; border-color:#334155; }

table { width:100%; border-collapse:collapse; font-size:0.85rem; }
th { text-align:left; padding:0.5rem 0.75rem; color:#94a3b8; font-weight:500; border-bottom:1px solid #1e293b; font-size:0.75rem; text-transform:uppercase; letter-spacing:0.05em; }
td { padding:0.6rem 0.75rem; border-bottom:1px solid rgba(30,41,59,0.5); }
tr:hover td { background:rgba(30,41,59,0.4); }
tr { cursor:pointer; }

.rank-cell { color:#fbbf24; font-weight:700; width:3rem; text-align:center; }
.cred-cell { color:#fbbf24; font-weight:600; }
.grain-cell { color:#d97706; }
.module-badge { display:inline-block; padding:0.1rem 0.4rem; border-radius:0.25rem; font-size:0.7rem; font-weight:500; }

.empty { text-align:center; padding:3rem; color:#64748b; }
.empty h3 { font-size:1.1rem; color:#94a3b8; margin-bottom:0.5rem; }

.error { background:rgba(239,68,68,0.1); border:1px solid rgba(239,68,68,0.3); border-radius:0.375rem; padding:0.5rem 0.75rem; color:#f87171; font-size:0.8rem; margin-bottom:1rem; }
.loading { text-align:center; padding:2rem; color:#64748b; }

/* Contributor detail */
.detail-header { margin-bottom:1.5rem; }
.detail-header h2 { font-size:1.3rem; margin-bottom:0.25rem; }
.detail-stats { display:flex; gap:1.5rem; margin-top:0.75rem; flex-wrap:wrap; }
.stat-card { background:#0f172a; border:1px solid #1e293b; border-radius:0.5rem; padding:0.75rem 1rem; min-width:120px; }
.stat-value { font-size:1.5rem; font-weight:700; }
.stat-label { font-size:0.7rem; color:#64748b; text-transform:uppercase; letter-spacing:0.05em; margin-top:0.25rem; }

.breakdown { margin-top:1.5rem; }
.breakdown h3 { font-size:0.95rem; margin-bottom:0.75rem; color:#94a3b8; }
.bar-row { display:flex; align-items:center; gap:0.5rem; margin-bottom:0.4rem; }
.bar-label { width:60px; font-size:0.75rem; color:#94a3b8; text-align:right; }
.bar-track { flex:1; height:20px; background:#0f172a; border-radius:0.25rem; overflow:hidden; }
.bar-fill { height:100%; border-radius:0.25rem; display:flex; align-items:center; padding-left:0.4rem; font-size:0.65rem; font-weight:600; min-width:fit-content; }
.bar-pct { width:40px; font-size:0.75rem; color:#64748b; }

/* Config */
.weight-row { display:flex; align-items:center; gap:0.75rem; margin-bottom:0.5rem; padding:0.4rem 0; }
.weight-label { width:160px; font-size:0.8rem; color:#94a3b8; }
.weight-slider { flex:1; -webkit-appearance:none; height:6px; background:#1e293b; border-radius:3px; outline:none; }
.weight-slider::-webkit-slider-thumb { -webkit-appearance:none; width:16px; height:16px; border-radius:50%; background:#f59e0b; cursor:pointer; }
.weight-value { width:40px; text-align:right; font-size:0.85rem; font-weight:600; color:#fbbf24; }

@media (max-width:640px) {
  .container { padding:1rem; }
  .detail-stats { gap:0.75rem; }
  .stat-card { min-width:90px; padding:0.5rem 0.75rem; }
  .stat-value { font-size:1.2rem; }
}
</style>
<div class="container">${content}</div>`;

		// Re-attach event listeners
		this.shadow.querySelectorAll('[data-action]').forEach(el => {
			el.addEventListener('click', (e) => {
				const action = (e.currentTarget as HTMLElement).dataset.action!;
				if (action === 'recompute') this.triggerRecompute();
				else if (action === 'config') this.loadConfig();
				else if (action === 'leaderboard') { this.view = 'leaderboard'; this.render(); }
				else if (action.startsWith('contributor:')) this.loadContributor(action.slice(12));
				else if (action === 'save-weights') this.saveWeights();
			});
		});

		// Weight slider listeners
		this.shadow.querySelectorAll('input[data-weight]').forEach(el => {
			el.addEventListener('input', (e) => {
				const input = e.target as HTMLInputElement;
				const key = input.dataset.weight!;
				if (this.config?.weights[key]) {
					this.config.weights[key].weight = parseFloat(input.value);
					const valueEl = this.shadow.querySelector(`[data-weight-value="${key}"]`);
					if (valueEl) valueEl.textContent = input.value;
				}
			});
		});
	}

	private renderLeaderboard(timeAgo: (ts: number) => string): string {
		if (this.loading) return '<div class="loading">Loading scores...</div>';

		const hasScores = this.scores.length > 0;

		return `
<div class="header">
  <h1><span style="font-size:1.8rem">⭐</span> Contribution Recognition</h1>
  <div class="header-actions">
    <button data-action="config">Weights</button>
    <button class="primary" data-action="recompute" ${this.recomputing ? 'disabled' : ''}>
      ${this.recomputing ? 'Computing...' : 'Recompute'}
    </button>
  </div>
</div>

${this.error ? `<div class="error">${this.error}</div>` : ''}

<div class="meta">
  ${hasScores ? `Last computed: ${timeAgo(this.computedAt)} · ${this.scores.length} contributors · ${this.totalCred} total cred` : ''}
</div>

${hasScores ? `
<table>
  <thead>
    <tr>
      <th style="width:3rem;text-align:center">#</th>
      <th>Contributor</th>
      <th style="text-align:right">Cred ⭐</th>
      <th style="text-align:right">Grain 🌾</th>
      <th>Top Module</th>
      <th style="text-align:right">Active</th>
    </tr>
  </thead>
  <tbody>
    ${this.scores.map(s => `
    <tr data-action="contributor:${s.did}">
      <td class="rank-cell">${s.rank}</td>
      <td style="font-weight:500">${this.escapeHtml(s.label || s.did.slice(0, 20))}</td>
      <td class="cred-cell" style="text-align:right">${s.cred}</td>
      <td class="grain-cell" style="text-align:right">${s.grainLifetime}</td>
      <td>
        ${s.topModule ? `<span class="module-badge" style="background:${MODULE_COLORS[s.topModule] || '#334155'}22;color:${MODULE_COLORS[s.topModule] || '#94a3b8'}">${MODULE_LABELS[s.topModule] || s.topModule}</span>` : '—'}
      </td>
      <td style="text-align:right;color:#64748b;font-size:0.8rem">${timeAgo(s.lastActive)}</td>
    </tr>`).join('')}
  </tbody>
</table>
` : `
<div class="empty">
  <h3>No scores yet</h3>
  <p>Click <strong>Recompute</strong> to build the contribution graph and run CredRank.</p>
  <p style="margin-top:0.75rem;font-size:0.8rem;color:#475569">
    Scores are generated from activity across Tasks, Docs, Chats, Calendar, Voting, Flows, Time, and Wallet modules.
  </p>
</div>
`}`;
	}

	private renderContributor(timeAgo: (ts: number) => string): string {
		if (this.loading) return '<div class="loading">Loading contributor...</div>';
		const c = this.selectedContributor;
		if (!c) return '<div class="error">Contributor not found</div>';

		const breakdown = Object.entries(c.breakdown).sort(([, a], [, b]) => b - a);

		return `
<div class="tab-bar">
  <div class="tab" data-action="leaderboard">← Back</div>
</div>

<div class="detail-header">
  <h2>${this.escapeHtml(c.label || c.did.slice(0, 20))}</h2>
  <div style="font-size:0.8rem;color:#64748b;word-break:break-all">${this.escapeHtml(c.did)}</div>

  <div class="detail-stats">
    <div class="stat-card">
      <div class="stat-value" style="color:#fbbf24">${c.cred}</div>
      <div class="stat-label">Cred ⭐</div>
    </div>
    <div class="stat-card">
      <div class="stat-value" style="color:#d97706">${c.grainLifetime}</div>
      <div class="stat-label">Grain 🌾</div>
    </div>
    <div class="stat-card">
      <div class="stat-value" style="color:#94a3b8">${timeAgo(c.lastActive)}</div>
      <div class="stat-label">Last Active</div>
    </div>
  </div>
</div>

<div class="breakdown">
  <h3>Contribution Breakdown</h3>
  ${breakdown.map(([mod, pct]) => `
  <div class="bar-row">
    <div class="bar-label">${MODULE_LABELS[mod] || mod}</div>
    <div class="bar-track">
      <div class="bar-fill" style="width:${Math.max(pct, 2)}%;background:${MODULE_COLORS[mod] || '#64748b'}">
        ${pct >= 10 ? MODULE_LABELS[mod] || mod : ''}
      </div>
    </div>
    <div class="bar-pct">${pct}%</div>
  </div>`).join('')}
</div>`;
	}

	private renderConfig(): string {
		if (this.loading) return '<div class="loading">Loading config...</div>';
		if (!this.config) return '<div class="error">Config not loaded</div>';

		const weights = Object.entries(this.config.weights).sort(([, a], [, b]) => b.weight - a.weight);

		return `
<div class="tab-bar">
  <div class="tab" data-action="leaderboard">← Back</div>
  <div class="tab active">Weight Configuration</div>
</div>

${this.error ? `<div class="error">${this.error}</div>` : ''}

<div style="margin-bottom:1.5rem">
  <h2 style="font-size:1.2rem;margin-bottom:0.25rem">Contribution Weights</h2>
  <p style="font-size:0.8rem;color:#64748b">Adjust how much each type of contribution counts toward Cred. Changes take effect on next recompute.</p>
</div>

${weights.map(([key, w]) => `
<div class="weight-row">
  <div class="weight-label" title="${this.escapeHtml(w.description)}">${this.escapeHtml(w.description)}</div>
  <input type="range" class="weight-slider" min="0" max="10" step="0.1" value="${w.weight}" data-weight="${key}">
  <div class="weight-value" data-weight-value="${key}">${w.weight}</div>
</div>`).join('')}

<div style="margin-top:1.5rem;display:flex;gap:0.5rem">
  <button class="primary" data-action="save-weights">Save Weights</button>
  <button data-action="leaderboard">Cancel</button>
</div>

<div style="margin-top:2rem;padding-top:1rem;border-top:1px solid #1e293b">
  <h3 style="font-size:0.95rem;color:#94a3b8;margin-bottom:0.75rem">Grain Parameters</h3>
  <div style="display:grid;grid-template-columns:1fr 1fr;gap:0.5rem;font-size:0.8rem;color:#64748b">
    <div>Grain per epoch: <strong style="color:#e2e8f0">${this.config.grainPerEpoch}</strong></div>
    <div>Epoch length: <strong style="color:#e2e8f0">${this.config.epochLengthDays} days</strong></div>
    <div>Slow fraction: <strong style="color:#e2e8f0">${(this.config.slowFraction * 100).toFixed(0)}%</strong></div>
    <div>Fast fraction: <strong style="color:#e2e8f0">${(this.config.fastFraction * 100).toFixed(0)}%</strong></div>
    <div>Damping factor: <strong style="color:#e2e8f0">${this.config.dampingFactor}</strong></div>
    <div>Lookback: <strong style="color:#e2e8f0">${this.config.lookbackDays} days</strong></div>
  </div>
</div>`;
	}

	private escapeHtml(s: string): string {
		return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
	}
}

customElements.define('folk-cred-dashboard', FolkCredDashboard);
