/**
 * <folk-feeds-dashboard> — RSS feed timeline + source management + activity + profiles.
 *
 * Subscribes to Automerge doc for real-time updates.
 * Falls back to REST API when offline runtime unavailable.
 */

import { feedsSchema, feedsDocId } from '../schemas';
import type { FeedsDoc, FeedSource, FeedItem, ManualPost, ActivityModuleConfig } from '../schemas';
import type { DocumentId } from '../../../shared/local-first/document';

interface TimelineEntry {
	type: 'item' | 'post' | 'activity';
	id: string;
	title: string;
	url: string;
	summary: string;
	author: string;
	sourceName: string;
	sourceColor: string;
	publishedAt: number;
	reshared: boolean;
	moduleId?: string;
	itemType?: string;
}

interface UserProfile {
	did: string;
	displayName: string;
	bio: string;
	publishModules: string[];
	createdAt: number;
	updatedAt: number;
}

export class FolkFeedsDashboard extends HTMLElement {
	private _space = 'demo';
	private _doc: FeedsDoc | null = null;
	private _timeline: TimelineEntry[] = [];
	private _sources: FeedSource[] = [];
	private _offlineUnsub: (() => void) | null = null;
	private _subscribedDocIds: string[] = [];
	private _tab: 'timeline' | 'sources' | 'modules' | 'profile' = 'timeline';
	private _loading = true;
	private _activityConfig: Record<string, ActivityModuleConfig> = {};
	private _profile: UserProfile | null = null;

	static get observedAttributes() { return ['space']; }

	connectedCallback() {
		if (!this.shadowRoot) this.attachShadow({ mode: 'open' });
		this._space = this.getAttribute('space') || 'demo';
		this.render();
		this.subscribeOffline();
	}

	disconnectedCallback() {
		this._offlineUnsub?.();
		this._offlineUnsub = null;
		const runtime = (window as any).__rspaceOfflineRuntime;
		if (runtime) {
			for (const id of this._subscribedDocIds) runtime.unsubscribe(id);
		}
		this._subscribedDocIds = [];
	}

	attributeChangedCallback(name: string, _old: string, val: string) {
		if (name === 'space') this._space = val;
	}

	private async subscribeOffline() {
		let runtime = (window as any).__rspaceOfflineRuntime;
		if (!runtime) {
			await new Promise(r => setTimeout(r, 200));
			runtime = (window as any).__rspaceOfflineRuntime;
		}
		if (!runtime) { await this.loadFromAPI(); return; }
		if (!runtime.isInitialized && runtime.init) {
			try { await runtime.init(); } catch { /* already init'd */ }
		}
		if (!runtime.isInitialized) { await this.loadFromAPI(); return; }

		try {
			const docId = feedsDocId(this._space) as DocumentId;
			const doc = await runtime.subscribe(docId, feedsSchema);
			this._subscribedDocIds.push(docId);
			this.renderFromDoc(doc);

			this._offlineUnsub = runtime.onChange(docId, (updated: any) => {
				this.renderFromDoc(updated);
			});
		} catch {
			await this.loadFromAPI();
		}
	}

	private async loadFromAPI() {
		try {
			const base = (window as any).__rspaceBase || '';
			const [tlRes, srcRes, actRes] = await Promise.all([
				fetch(`${base}/${this._space}/rfeeds/api/timeline?limit=50`),
				fetch(`${base}/${this._space}/rfeeds/api/sources`),
				fetch(`${base}/${this._space}/rfeeds/api/activity/config`),
			]);
			if (tlRes.ok) this._timeline = await tlRes.json();
			if (srcRes.ok) this._sources = await srcRes.json();
			if (actRes.ok) this._activityConfig = await actRes.json();
		} catch { /* offline */ }

		// Load profile if authenticated
		await this.loadProfile();

		this._loading = false;
		this.render();
	}

	private async loadProfile() {
		const token = this.getToken();
		if (!token) return;
		try {
			const base = (window as any).__rspaceBase || '';
			const res = await fetch(`${base}/${this._space}/rfeeds/api/profile`, {
				headers: { Authorization: `Bearer ${token}` },
			});
			if (res.ok) this._profile = await res.json();
		} catch { /* ignore */ }
	}

	private renderFromDoc(doc: FeedsDoc) {
		if (!doc) return;
		this._doc = doc;
		this._loading = false;

		// Build timeline from doc
		const entries: TimelineEntry[] = [];
		if (doc.items) {
			for (const item of Object.values(doc.items)) {
				const source = doc.sources?.[item.sourceId];
				if (!source?.enabled) continue;
				entries.push({
					type: 'item',
					id: item.id,
					title: item.title,
					url: item.url,
					summary: item.summary,
					author: item.author,
					sourceName: source?.name || 'Unknown',
					sourceColor: source?.color || '#94a3b8',
					publishedAt: item.publishedAt,
					reshared: item.reshared,
				});
			}
		}
		if (doc.posts) {
			for (const post of Object.values(doc.posts)) {
				entries.push({
					type: 'post',
					id: post.id,
					title: '',
					url: '',
					summary: post.content,
					author: post.authorName || post.authorDid,
					sourceName: 'Manual Post',
					sourceColor: '#67e8f9',
					publishedAt: post.createdAt,
					reshared: true,
				});
			}
		}
		// Activity cache entries
		if (doc.activityCache) {
			for (const entry of Object.values(doc.activityCache)) {
				const config = doc.settings?.activityModules?.[entry.moduleId];
				if (config && !config.enabled) continue;
				entries.push({
					type: 'activity',
					id: entry.id,
					title: entry.title,
					url: entry.url,
					summary: entry.summary,
					author: entry.author,
					sourceName: config?.label || entry.moduleId,
					sourceColor: config?.color || '#94a3b8',
					publishedAt: entry.publishedAt,
					reshared: entry.reshared,
					moduleId: entry.moduleId,
					itemType: entry.itemType,
				});
			}
		}
		entries.sort((a, b) => b.publishedAt - a.publishedAt);
		this._timeline = entries;
		this._sources = doc.sources ? Object.values(doc.sources).sort((a, b) => b.addedAt - a.addedAt) : [];

		// Extract activity config from doc
		if (doc.settings?.activityModules) {
			this._activityConfig = { ...doc.settings.activityModules };
		}
		// Extract profile
		if (doc.userProfiles) {
			const token = this.getToken();
			if (token) {
				try {
					const payload = JSON.parse(atob(token.split('.')[1]));
					const did = payload.did || payload.sub;
					if (did && doc.userProfiles[did]) {
						this._profile = doc.userProfiles[did] as any;
					}
				} catch { /* ignore */ }
			}
		}

		this.render();
	}

	private getToken(): string | null {
		try {
			const raw = localStorage.getItem('encryptid-session');
			if (!raw) return null;
			const parsed = JSON.parse(raw);
			return parsed?.token || parsed?.jwt || null;
		} catch { return null; }
	}

	private async addSource() {
		const root = this.shadowRoot!;
		const urlInput = root.querySelector<HTMLInputElement>('#add-source-url');
		const nameInput = root.querySelector<HTMLInputElement>('#add-source-name');
		if (!urlInput?.value) return;

		const token = this.getToken();
		if (!token) { alert('Sign in to add feeds'); return; }

		const base = (window as any).__rspaceBase || '';
		const res = await fetch(`${base}/${this._space}/rfeeds/api/sources`, {
			method: 'POST',
			headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
			body: JSON.stringify({ url: urlInput.value, name: nameInput?.value || undefined }),
		});

		if (res.ok) {
			urlInput.value = '';
			if (nameInput) nameInput.value = '';
			if (!this._doc) await this.loadFromAPI();
		} else {
			const err = await res.json().catch(() => ({ error: 'Failed' }));
			alert(err.error || 'Failed to add source');
		}
	}

	private async deleteSource(id: string) {
		if (!confirm('Remove this feed source and all its items?')) return;
		const token = this.getToken();
		if (!token) return;
		const base = (window as any).__rspaceBase || '';
		await fetch(`${base}/${this._space}/rfeeds/api/sources/${id}`, {
			method: 'DELETE',
			headers: { Authorization: `Bearer ${token}` },
		});
		if (!this._doc) await this.loadFromAPI();
	}

	private async syncSource(id: string) {
		const token = this.getToken();
		if (!token) return;
		const base = (window as any).__rspaceBase || '';
		const btn = this.shadowRoot?.querySelector<HTMLButtonElement>(`[data-sync="${id}"]`);
		if (btn) { btn.disabled = true; btn.textContent = 'Syncing\u2026'; }

		const res = await fetch(`${base}/${this._space}/rfeeds/api/sources/${id}/sync`, {
			method: 'POST',
			headers: { Authorization: `Bearer ${token}` },
		});
		const result = await res.json().catch(() => ({}));

		if (btn) {
			btn.disabled = false;
			btn.textContent = result.added ? `+${result.added}` : 'Sync';
			setTimeout(() => { btn.textContent = 'Sync'; }, 2000);
		}
		if (!this._doc) await this.loadFromAPI();
	}

	private async toggleReshare(id: string, type: 'item' | 'activity') {
		const token = this.getToken();
		if (!token) return;
		const base = (window as any).__rspaceBase || '';
		const endpoint = type === 'activity'
			? `${base}/${this._space}/rfeeds/api/activity/${encodeURIComponent(id)}/reshare`
			: `${base}/${this._space}/rfeeds/api/items/${id}/reshare`;
		await fetch(endpoint, {
			method: 'PATCH',
			headers: { Authorization: `Bearer ${token}` },
		});
		if (!this._doc) await this.loadFromAPI();
	}

	private async createPost() {
		const root = this.shadowRoot!;
		const textarea = root.querySelector<HTMLTextAreaElement>('#post-content');
		if (!textarea?.value.trim()) return;

		const token = this.getToken();
		if (!token) { alert('Sign in to post'); return; }

		const base = (window as any).__rspaceBase || '';
		const res = await fetch(`${base}/${this._space}/rfeeds/api/posts`, {
			method: 'POST',
			headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
			body: JSON.stringify({ content: textarea.value }),
		});

		if (res.ok) {
			textarea.value = '';
			if (!this._doc) await this.loadFromAPI();
		}
	}

	private async importOPML() {
		const input = document.createElement('input');
		input.type = 'file';
		input.accept = '.opml,.xml';
		input.onchange = async () => {
			const file = input.files?.[0];
			if (!file) return;
			const token = this.getToken();
			if (!token) { alert('Sign in to import'); return; }

			const opml = await file.text();
			const base = (window as any).__rspaceBase || '';
			const res = await fetch(`${base}/${this._space}/rfeeds/api/sources/import-opml`, {
				method: 'POST',
				headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
				body: JSON.stringify({ opml }),
			});
			const result = await res.json().catch(() => ({}));
			if (result.added) alert(`Imported ${result.added} feeds`);
			if (!this._doc) await this.loadFromAPI();
		};
		input.click();
	}

	private async syncActivity() {
		const token = this.getToken();
		if (!token) { alert('Sign in to sync activity'); return; }

		const btn = this.shadowRoot?.querySelector<HTMLButtonElement>('#sync-activity-btn');
		if (btn) { btn.disabled = true; btn.textContent = 'Syncing\u2026'; }

		const base = (window as any).__rspaceBase || '';
		const res = await fetch(`${base}/${this._space}/rfeeds/api/activity/sync`, {
			method: 'POST',
			headers: { Authorization: `Bearer ${token}` },
		});
		const result = await res.json().catch(() => ({}));

		if (btn) {
			btn.disabled = false;
			btn.textContent = result.synced ? `Synced ${result.synced}` : 'Sync Activity';
			setTimeout(() => { btn.textContent = 'Sync Activity'; }, 3000);
		}
		if (!this._doc) await this.loadFromAPI();
	}

	private async toggleModule(moduleId: string, enabled: boolean) {
		const token = this.getToken();
		if (!token) return;

		const current = this._activityConfig[moduleId] || { enabled: false, label: moduleId, color: '#94a3b8' };
		const base = (window as any).__rspaceBase || '';
		const res = await fetch(`${base}/${this._space}/rfeeds/api/activity/config`, {
			method: 'PUT',
			headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
			body: JSON.stringify({ modules: { [moduleId]: { ...current, enabled } } }),
		});
		if (res.ok) {
			this._activityConfig = await res.json();
			this.render();
		}
	}

	private async saveProfile() {
		const token = this.getToken();
		if (!token) { alert('Sign in to save profile'); return; }

		const root = this.shadowRoot!;
		const displayName = root.querySelector<HTMLInputElement>('#profile-name')?.value || '';
		const bio = root.querySelector<HTMLTextAreaElement>('#profile-bio')?.value || '';
		const publishModules: string[] = [];
		root.querySelectorAll<HTMLInputElement>('[data-publish-module]').forEach((cb) => {
			if (cb.checked) publishModules.push(cb.dataset.publishModule!);
		});

		const base = (window as any).__rspaceBase || '';
		const res = await fetch(`${base}/${this._space}/rfeeds/api/profile`, {
			method: 'PUT',
			headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
			body: JSON.stringify({ displayName, bio, publishModules }),
		});
		if (res.ok) {
			this._profile = await res.json();
			this.render();
			// Flash save button
			const btn = this.shadowRoot?.querySelector<HTMLButtonElement>('#save-profile-btn');
			if (btn) { btn.textContent = 'Saved!'; setTimeout(() => { btn.textContent = 'Save Profile'; }, 2000); }
		}
	}

	private timeAgo(ts: number): string {
		const diff = Date.now() - ts;
		if (diff < 60_000) return 'just now';
		if (diff < 3600_000) return `${Math.floor(diff / 60_000)}m ago`;
		if (diff < 86400_000) return `${Math.floor(diff / 3600_000)}h ago`;
		if (diff < 2592000_000) return `${Math.floor(diff / 86400_000)}d ago`;
		return new Date(ts).toLocaleDateString();
	}

	private render() {
		const root = this.shadowRoot!;
		const activityCount = this._timeline.filter(e => e.type === 'activity').length;

		root.innerHTML = `
<style>
:host { display:block; max-width:720px; margin:0 auto; padding:16px; font-family:system-ui,-apple-system,sans-serif; color:#e2e8f0; }
* { box-sizing:border-box; }
.tabs { display:flex; gap:4px; margin-bottom:16px; flex-wrap:wrap; }
.tab { padding:8px 16px; border:1px solid #334155; border-radius:8px; background:transparent; color:#94a3b8; cursor:pointer; font-size:14px; }
.tab.active { background:#1e293b; color:#e2e8f0; border-color:#475569; }
.tab:hover { background:#1e293b; }
.composer { display:flex; gap:8px; margin-bottom:20px; }
.composer textarea { flex:1; background:#0f172a; border:1px solid #334155; border-radius:8px; padding:10px; color:#e2e8f0; resize:none; font-size:14px; min-height:60px; }
.composer textarea:focus { outline:none; border-color:#67e8f9; }
.btn { padding:8px 14px; border:none; border-radius:6px; cursor:pointer; font-size:13px; font-weight:500; }
.btn-primary { background:#0891b2; color:#fff; }
.btn-primary:hover { background:#06b6d4; }
.btn-sm { padding:4px 10px; font-size:12px; }
.btn-ghost { background:transparent; border:1px solid #334155; color:#94a3b8; }
.btn-ghost:hover { background:#1e293b; color:#e2e8f0; }
.btn-danger { background:#7f1d1d; color:#fca5a5; }
.btn-danger:hover { background:#991b1b; }

/* Timeline items */
.entry { padding:14px 16px; border:1px solid #1e293b; border-radius:10px; margin-bottom:10px; background:#0f172a; transition:border-color .15s; }
.entry:hover { border-color:#334155; }
.entry-header { display:flex; justify-content:space-between; align-items:center; margin-bottom:6px; gap:8px; }
.source-badge { font-size:11px; padding:2px 8px; border-radius:99px; font-weight:500; white-space:nowrap; }
.entry-title { font-size:15px; font-weight:600; color:#f1f5f9; }
.entry-title a { color:inherit; text-decoration:none; }
.entry-title a:hover { text-decoration:underline; }
.entry-summary { font-size:13px; color:#94a3b8; line-height:1.5; margin-top:4px; }
.entry-meta { display:flex; gap:12px; align-items:center; margin-top:8px; font-size:12px; color:#64748b; }
.reshare-btn { cursor:pointer; padding:2px 8px; border-radius:4px; border:1px solid #334155; background:transparent; color:#64748b; font-size:11px; }
.reshare-btn.active { background:#164e63; border-color:#0891b2; color:#67e8f9; }
.reshare-btn:hover { border-color:#475569; }
.type-badge { font-size:10px; padding:1px 6px; border-radius:4px; background:#1e293b; color:#64748b; text-transform:uppercase; letter-spacing:0.5px; }

/* Sources */
.source-card { padding:14px 16px; border:1px solid #1e293b; border-radius:10px; margin-bottom:10px; background:#0f172a; }
.source-card-header { display:flex; justify-content:space-between; align-items:center; }
.source-name { font-size:14px; font-weight:600; color:#f1f5f9; }
.source-url { font-size:12px; color:#64748b; word-break:break-all; margin-top:2px; }
.source-status { font-size:12px; color:#64748b; margin-top:6px; display:flex; gap:12px; align-items:center; }
.source-error { color:#f87171; font-size:12px; margin-top:4px; }
.source-actions { display:flex; gap:6px; }
.add-source { display:flex; gap:8px; margin-bottom:16px; flex-wrap:wrap; }
.add-source input { flex:1; min-width:200px; background:#0f172a; border:1px solid #334155; border-radius:8px; padding:8px 12px; color:#e2e8f0; font-size:13px; }
.add-source input:focus { outline:none; border-color:#67e8f9; }
.add-source input::placeholder { color:#475569; }
.status-dot { width:8px; height:8px; border-radius:50%; display:inline-block; }
.status-dot.ok { background:#22c55e; }
.status-dot.err { background:#ef4444; }
.status-dot.pending { background:#eab308; }
.empty { text-align:center; color:#475569; padding:40px 0; font-size:14px; }
.loading { text-align:center; color:#475569; padding:40px 0; }
.toolbar { display:flex; justify-content:space-between; align-items:center; margin-bottom:12px; }

/* Modules config */
.module-row { display:flex; align-items:center; gap:12px; padding:10px 14px; border:1px solid #1e293b; border-radius:8px; margin-bottom:8px; background:#0f172a; }
.module-row label { flex:1; font-size:14px; color:#e2e8f0; cursor:pointer; display:flex; align-items:center; gap:8px; }
.module-color { width:12px; height:12px; border-radius:50%; }
.toggle { position:relative; width:36px; height:20px; }
.toggle input { opacity:0; width:0; height:0; }
.toggle .slider { position:absolute; cursor:pointer; top:0; left:0; right:0; bottom:0; background:#334155; border-radius:10px; transition:.2s; }
.toggle .slider:before { content:""; position:absolute; height:16px; width:16px; left:2px; bottom:2px; background:#94a3b8; border-radius:50%; transition:.2s; }
.toggle input:checked + .slider { background:#0891b2; }
.toggle input:checked + .slider:before { transform:translateX(16px); background:#fff; }

/* Profile */
.profile-form { display:flex; flex-direction:column; gap:12px; }
.profile-form label { font-size:13px; color:#94a3b8; }
.profile-form input, .profile-form textarea { background:#0f172a; border:1px solid #334155; border-radius:8px; padding:8px 12px; color:#e2e8f0; font-size:14px; }
.profile-form input:focus, .profile-form textarea:focus { outline:none; border-color:#67e8f9; }
.profile-form textarea { resize:vertical; min-height:60px; }
.publish-modules { display:flex; flex-wrap:wrap; gap:8px; margin-top:4px; }
.publish-modules label { display:flex; align-items:center; gap:6px; padding:4px 10px; border:1px solid #334155; border-radius:6px; font-size:13px; color:#94a3b8; cursor:pointer; }
.publish-modules label:has(input:checked) { border-color:#0891b2; color:#67e8f9; background:#164e6322; }
.publish-modules input { accent-color:#0891b2; }
.feed-url { margin-top:12px; padding:10px 14px; background:#0f172a; border:1px solid #334155; border-radius:8px; font-size:12px; color:#64748b; word-break:break-all; }
.feed-url a { color:#67e8f9; }
.section-title { font-size:13px; font-weight:600; color:#94a3b8; text-transform:uppercase; letter-spacing:0.5px; margin-bottom:8px; margin-top:16px; }
</style>

<div class="tabs">
	<button class="tab ${this._tab === 'timeline' ? 'active' : ''}" data-tab="timeline">Timeline</button>
	<button class="tab ${this._tab === 'sources' ? 'active' : ''}" data-tab="sources">Sources (${this._sources.length})</button>
	<button class="tab ${this._tab === 'modules' ? 'active' : ''}" data-tab="modules">Modules${activityCount ? ` (${activityCount})` : ''}</button>
	<button class="tab ${this._tab === 'profile' ? 'active' : ''}" data-tab="profile">My Profile</button>
</div>

${this._loading ? '<div class="loading">Loading feeds\u2026</div>' :
	this._tab === 'timeline' ? this.renderTimeline() :
	this._tab === 'sources' ? this.renderSources() :
	this._tab === 'modules' ? this.renderModules() :
	this.renderProfile()}
`;

		// Event listeners
		root.querySelectorAll('.tab').forEach((btn) => {
			btn.addEventListener('click', () => {
				this._tab = (btn as HTMLElement).dataset.tab as any;
				this.render();
			});
		});

		const postBtn = root.querySelector('#post-btn');
		postBtn?.addEventListener('click', () => this.createPost());

		const addBtn = root.querySelector('#add-source-btn');
		addBtn?.addEventListener('click', () => this.addSource());

		const importBtn = root.querySelector('#import-opml-btn');
		importBtn?.addEventListener('click', () => this.importOPML());

		const syncActBtn = root.querySelector('#sync-activity-btn');
		syncActBtn?.addEventListener('click', () => this.syncActivity());

		const saveProfileBtn = root.querySelector('#save-profile-btn');
		saveProfileBtn?.addEventListener('click', () => this.saveProfile());

		root.querySelectorAll('[data-sync]').forEach((btn) => {
			btn.addEventListener('click', () => this.syncSource((btn as HTMLElement).dataset.sync!));
		});

		root.querySelectorAll('[data-delete-source]').forEach((btn) => {
			btn.addEventListener('click', () => this.deleteSource((btn as HTMLElement).dataset.deleteSource!));
		});

		root.querySelectorAll('[data-reshare]').forEach((btn) => {
			const el = btn as HTMLElement;
			btn.addEventListener('click', () => this.toggleReshare(el.dataset.reshare!, (el.dataset.reshareType as any) || 'item'));
		});

		root.querySelectorAll('[data-toggle-module]').forEach((input) => {
			input.addEventListener('change', () => {
				const el = input as HTMLInputElement;
				this.toggleModule(el.dataset.toggleModule!, el.checked);
			});
		});
	}

	private renderTimeline(): string {
		const composer = `
<div class="composer">
	<textarea id="post-content" placeholder="Write a post\u2026" rows="2"></textarea>
	<button class="btn btn-primary" id="post-btn">Post</button>
</div>`;

		if (!this._timeline.length) {
			return composer + '<div class="empty">No feed items yet. Add some sources in the Sources tab!</div>';
		}

		const items = this._timeline.slice(0, 100).map((e) => `
<div class="entry">
	<div class="entry-header">
		<span class="source-badge" style="background:${e.sourceColor}22; color:${e.sourceColor}">${this.escHtml(e.sourceName)}</span>
		<span style="font-size:12px;color:#64748b">
			${e.type === 'activity' && e.itemType ? `<span class="type-badge">${this.escHtml(e.itemType)}</span> ` : ''}
			${this.timeAgo(e.publishedAt)}
		</span>
	</div>
	${e.title ? `<div class="entry-title">${e.url ? `<a href="${this.escHtml(e.url)}" target="_blank" rel="noopener">${this.escHtml(e.title)}</a>` : this.escHtml(e.title)}</div>` : ''}
	<div class="entry-summary">${this.escHtml(e.summary)}</div>
	<div class="entry-meta">
		${e.author ? `<span>${this.escHtml(e.author)}</span>` : ''}
		${e.type === 'item' ? `<button class="reshare-btn ${e.reshared ? 'active' : ''}" data-reshare="${e.id}" data-reshare-type="item">${e.reshared ? 'Shared' : 'Reshare'}</button>` : ''}
		${e.type === 'activity' ? `<button class="reshare-btn ${e.reshared ? 'active' : ''}" data-reshare="${e.id}" data-reshare-type="activity">${e.reshared ? 'Shared' : 'Reshare'}</button>` : ''}
	</div>
</div>`).join('');

		return composer + items;
	}

	private renderSources(): string {
		const addForm = `
<div class="add-source">
	<input type="url" id="add-source-url" placeholder="Feed URL (RSS or Atom)">
	<input type="text" id="add-source-name" placeholder="Display name (optional)" style="max-width:180px">
	<button class="btn btn-primary" id="add-source-btn">Add Feed</button>
	<button class="btn btn-ghost" id="import-opml-btn">Import OPML</button>
</div>`;

		if (!this._sources.length) {
			return addForm + '<div class="empty">No feed sources yet. Add one above!</div>';
		}

		const cards = this._sources.map((s) => {
			const dotClass = s.lastError ? 'err' : s.lastFetchedAt ? 'ok' : 'pending';
			return `
<div class="source-card">
	<div class="source-card-header">
		<div>
			<div class="source-name" style="color:${s.color}">${this.escHtml(s.name)}</div>
			<div class="source-url">${this.escHtml(s.url)}</div>
		</div>
		<div class="source-actions">
			<button class="btn btn-sm btn-ghost" data-sync="${s.id}">Sync</button>
			<button class="btn btn-sm btn-danger" data-delete-source="${s.id}">\u2715</button>
		</div>
	</div>
	<div class="source-status">
		<span class="status-dot ${dotClass}"></span>
		<span>${s.itemCount} items</span>
		<span>${s.lastFetchedAt ? 'Last sync ' + this.timeAgo(s.lastFetchedAt) : 'Never synced'}</span>
		<span>${s.enabled ? 'Active' : 'Paused'}</span>
	</div>
	${s.lastError ? `<div class="source-error">${this.escHtml(s.lastError)}</div>` : ''}
</div>`;
		}).join('');

		return addForm + cards;
	}

	private renderModules(): string {
		const moduleIds = ['rcal', 'rtasks', 'rdocs', 'rnotes', 'rsocials', 'rwallet', 'rphotos', 'rfiles'];
		const defaultConfigs: Record<string, { label: string; color: string }> = {
			rcal: { label: 'Calendar', color: '#f59e0b' },
			rtasks: { label: 'Tasks', color: '#8b5cf6' },
			rdocs: { label: 'Docs', color: '#3b82f6' },
			rnotes: { label: 'Vaults', color: '#a78bfa' },
			rsocials: { label: 'Socials', color: '#ec4899' },
			rwallet: { label: 'Wallet', color: '#10b981' },
			rphotos: { label: 'Photos', color: '#f97316' },
			rfiles: { label: 'Files', color: '#64748b' },
		};

		const rows = moduleIds.map((id) => {
			const config = this._activityConfig[id] || { enabled: false, ...defaultConfigs[id] };
			const label = config.label || defaultConfigs[id]?.label || id;
			const color = config.color || defaultConfigs[id]?.color || '#94a3b8';
			return `
<div class="module-row">
	<label>
		<span class="module-color" style="background:${color}"></span>
		${this.escHtml(label)}
	</label>
	<div class="toggle">
		<input type="checkbox" data-toggle-module="${id}" ${config.enabled ? 'checked' : ''}>
		<span class="slider"></span>
	</div>
</div>`;
		}).join('');

		return `
<div class="toolbar">
	<div class="section-title">Activity Modules</div>
	<button class="btn btn-sm btn-primary" id="sync-activity-btn">Sync Activity</button>
</div>
<p style="font-size:13px;color:#64748b;margin:0 0 12px">Enable modules to pull their activity into the timeline. Admin only.</p>
${rows}`;
	}

	private renderProfile(): string {
		const token = this.getToken();
		if (!token) {
			return '<div class="empty">Sign in to manage your feed profile.</div>';
		}

		const p = this._profile;
		const moduleIds = ['rcal', 'rtasks', 'rdocs', 'rnotes', 'rsocials', 'rwallet', 'rphotos', 'rfiles', 'posts'];
		const labels: Record<string, string> = {
			rcal: 'Calendar', rtasks: 'Tasks', rdocs: 'Docs', rnotes: 'Vaults',
			rsocials: 'Socials', rwallet: 'Wallet', rphotos: 'Photos', rfiles: 'Files', posts: 'Manual Posts',
		};
		const publishSet = new Set(p?.publishModules || []);

		const checkboxes = moduleIds.map((id) => `
<label>
	<input type="checkbox" data-publish-module="${id}" ${publishSet.has(id) ? 'checked' : ''}>
	${labels[id] || id}
</label>`).join('');

		// Personal feed URL
		let feedUrlHtml = '';
		if (p?.did) {
			const host = window.location.origin;
			const feedUrl = `${host}/${this._space}/rfeeds/user/${encodeURIComponent(p.did)}/feed.xml`;
			feedUrlHtml = `
<div class="feed-url">
	Your personal feed: <a href="${this.escHtml(feedUrl)}" target="_blank">${this.escHtml(feedUrl)}</a>
	<br><span style="color:#475569">Others can subscribe to this URL from any RSS reader or rFeeds space.</span>
</div>`;
		}

		return `
<div class="profile-form">
	<div>
		<label>Display Name</label>
		<input type="text" id="profile-name" value="${this.escHtml(p?.displayName || '')}" placeholder="Your name">
	</div>
	<div>
		<label>Bio</label>
		<textarea id="profile-bio" placeholder="A short bio\u2026" rows="2">${this.escHtml(p?.bio || '')}</textarea>
	</div>
	<div>
		<label>Publish to Personal Feed</label>
		<div class="publish-modules">${checkboxes}</div>
	</div>
	<button class="btn btn-primary" id="save-profile-btn">Save Profile</button>
	${feedUrlHtml}
</div>`;
	}

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

customElements.define('folk-feeds-dashboard', FolkFeedsDashboard);
