import { CommonModule } from '@angular/common';
import { Component, HostListener, OnDestroy, OnInit, inject } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { Subscription, catchError, firstValueFrom, interval, of, switchMap, takeWhile } from 'rxjs';

import { AdminApiService } from './admin-api.service';
import { AuditLog, BuildJob, Post, PostInput, PostStatus, User } from './models';

type FeedbackTone = 'success' | 'info' | 'error';

@Component({
  selector: 'osaet-root',
  standalone: true,
  imports: [CommonModule, FormsModule],
  templateUrl: './app.component.html',
  styleUrl: './app.component.css'
})
export class AppComponent implements OnInit, OnDestroy {
  private readonly api = inject(AdminApiService);

  user: User | null = null;
  posts: Post[] = [];
  auditLogs: AuditLog[] = [];
  currentPost: Post | null = null;
  statusFilter: PostStatus | '' = '';
  auditActionFilter = '';
  auditResourceFilter = '';
  auditQuery = '';
  view: 'list' | 'editor' | 'logs' = 'list';
  editorMode: 'edit' | 'preview' | 'split' = 'edit';
  page = 1;
  auditPage = 1;
  readonly pageSize = 12;
  readonly auditPageSize = 20;
  totalPosts = 0;
  totalAuditLogs = 0;

  loginUsername = 'yarnom';
  loginPassword = '';
  loginMessage = '';
  editorMessage = '';
  feedback: { title: string; message: string; tone: FeedbackTone } | null = null;
  currentBuildJob: BuildJob | null = null;
  showBuildLog = false;
  loading = true;
  saving = false;
  generatingSlug = false;
  uploadingImage = false;
  autosaveStatus = '未修改';
  lastAutosavedAt: Date | null = null;

  draft = this.emptyDraft();
  tagsText = '';
  private savedSnapshot = '';
  private readonly autosaveIntervalMs = 12000;
  private autosaveSubscription: Subscription | null = null;
  private feedbackTimer: ReturnType<typeof setTimeout> | null = null;

  ngOnInit() {
    void this.bootstrap();
    this.autosaveSubscription = interval(this.autosaveIntervalMs).subscribe(() => {
      void this.autosave();
    });
  }

  ngOnDestroy() {
    this.autosaveSubscription?.unsubscribe();
    if (this.feedbackTimer) {
      clearTimeout(this.feedbackTimer);
    }
  }

  async bootstrap() {
    try {
      const response = await firstValueFrom(this.api.me());
      this.user = response.user;
      await this.loadPosts();
    } catch {
      this.user = null;
    } finally {
      this.loading = false;
      this.rememberSavedState();
      this.updateAutosaveStatus();
    }
  }

  async login() {
    this.loginMessage = '';
    try {
      const response = await firstValueFrom(this.api.login(this.loginUsername.trim(), this.loginPassword));
      this.user = response.user;
      this.loginPassword = '';
      await this.loadPosts();
      this.newPost();
    } catch (error) {
      this.loginMessage = errorMessage(error);
    }
  }

  async logout() {
    if (!this.confirmDiscard()) {
      return;
    }
    await firstValueFrom(this.api.logout().pipe(catchError(() => of({ ok: true }))));
    this.user = null;
    this.posts = [];
    this.auditLogs = [];
    this.currentPost = null;
    this.draft = this.emptyDraft();
    this.tagsText = '';
    this.rememberSavedState();
    this.updateAutosaveStatus();
  }

  async loadPosts() {
    const response = await firstValueFrom(
      this.api.listPosts(this.statusFilter, this.pageSize, (this.page - 1) * this.pageSize)
    );
    this.posts = response.posts ?? [];
    this.totalPosts = response.total ?? 0;
    if (this.page > this.totalPages) {
      this.page = this.totalPages;
      await this.loadPosts();
    }
  }

  async loadAuditLogs() {
    const response = await firstValueFrom(
      this.api.listAuditLogs(
        this.auditActionFilter,
        this.auditResourceFilter,
        this.auditQuery.trim(),
        this.auditPageSize,
        (this.auditPage - 1) * this.auditPageSize
      )
    );
    this.auditLogs = response.logs ?? [];
    this.totalAuditLogs = response.total ?? 0;
    if (this.auditPage > this.totalAuditPages) {
      this.auditPage = this.totalAuditPages;
      await this.loadAuditLogs();
    }
  }

  async showLogs() {
    if (!this.confirmDiscard()) {
      return;
    }
    this.view = 'logs';
    await this.loadAuditLogs();
  }

  async selectPost(id: string) {
    if (!this.confirmDiscard()) {
      return;
    }
    const response = await firstValueFrom(this.api.getPost(id));
    this.currentPost = response.post;
    this.draft = this.postToInput(response.post);
    this.tagsText = this.draft.tags.join(', ');
    this.editorMessage = '';
    this.currentBuildJob = null;
    this.showBuildLog = false;
    this.rememberSavedState();
    this.updateAutosaveStatus();
    this.view = 'editor';
  }

  editPost(id: string, event: Event) {
    event.stopPropagation();
    void this.selectPost(id);
  }

  async deletePost(post: Post, event: Event) {
    event.stopPropagation();
    if (!window.confirm(`确定删除《${post.title || '未命名'}》吗？`)) {
      return;
    }

    try {
      const response = await firstValueFrom(this.api.deletePost(post.id));
      if (this.currentPost?.id === post.id) {
        this.currentPost = null;
        this.draft = this.emptyDraft();
        this.tagsText = '';
        this.view = 'list';
        this.rememberSavedState();
        this.updateAutosaveStatus();
      }
      await this.loadPosts();
      if (response.buildJob) {
        this.currentBuildJob = response.buildJob;
        this.editorMessage = '已删除，正在重新构建站点';
        this.watchBuildJob(response.buildJob);
      }
    } catch (error) {
      this.editorMessage = errorMessage(error);
    }
  }

  newPost() {
    if (!this.confirmDiscard()) {
      return;
    }
    this.currentPost = null;
    this.draft = this.emptyDraft();
    this.tagsText = '';
    this.editorMessage = '';
    this.currentBuildJob = null;
    this.showBuildLog = false;
    this.rememberSavedState();
    this.updateAutosaveStatus();
    this.view = 'editor';
  }

  async backToList() {
    if (!this.confirmDiscard()) {
      return;
    }
    this.view = 'list';
    await this.loadPosts();
  }

  async changeAuditFilter() {
    this.auditPage = 1;
    await this.loadAuditLogs();
  }

  async previousAuditPage() {
    if (this.auditPage <= 1) {
      return;
    }
    this.auditPage -= 1;
    await this.loadAuditLogs();
  }

  async nextAuditPage() {
    if (this.auditPage >= this.totalAuditPages) {
      return;
    }
    this.auditPage += 1;
    await this.loadAuditLogs();
  }

  async changeStatusFilter() {
    this.page = 1;
    await this.loadPosts();
  }

  async previousPage() {
    if (this.page <= 1) {
      return;
    }
    this.page -= 1;
    await this.loadPosts();
  }

  async nextPage() {
    if (this.page >= this.totalPages) {
      return;
    }
    this.page += 1;
    await this.loadPosts();
  }

  async goToPage(page: number) {
    if (page < 1 || page > this.totalPages || page === this.page) {
      return;
    }
    this.page = page;
    await this.loadPosts();
  }

  async savePost(silent = false) {
    this.saving = true;
    if (!silent) {
      this.editorMessage = '';
    }
    if (silent) {
      this.autosaveStatus = '自动保存中';
    }
    try {
      const input = this.normalizedDraft();
      const response = this.currentPost
        ? await firstValueFrom(this.api.updatePost(this.currentPost.id, input))
        : await firstValueFrom(this.api.createPost(input));
      this.currentPost = response.post;
      this.draft = this.postToInput(response.post);
      this.tagsText = this.draft.tags.join(', ');
      await this.loadPosts();
      if (!silent) {
        this.editorMessage = '已保存';
        this.showFeedback('保存成功', '文章内容已经保存。');
        this.lastAutosavedAt = null;
      }
      if (silent) {
        this.lastAutosavedAt = new Date();
      }
      this.rememberSavedState();
      this.updateAutosaveStatus();
      return response;
    } catch (error) {
      if (silent) {
        this.autosaveStatus = `自动保存失败：${errorMessage(error)}`;
      } else {
        const message = errorMessage(error);
        this.editorMessage = message;
        this.showFeedback('保存失败', message, 'error');
      }
      return null;
    } finally {
      this.saving = false;
    }
  }

  async buildPost() {
    const saved = await this.savePost();
    if (!saved) {
      return;
    }
    if (!this.currentPost) {
      return;
    }

    this.editorMessage = '';
    try {
      const response = await firstValueFrom(this.api.buildPost(this.currentPost.id));
      this.editorMessage = '已开始构建';
      this.showFeedback('构建已提交', '正在按照当前状态生成静态站点。', 'info');
      this.currentBuildJob = response.buildJob;
      this.showBuildLog = false;
      this.rememberSavedState();
      this.updateAutosaveStatus();
      this.watchBuildJob(response.buildJob);
    } catch (error) {
      const message = errorMessage(error);
      this.editorMessage = message;
      this.showFeedback('构建失败', message, 'error');
    }
  }

  watchBuildJob(job: BuildJob, notify = true) {
    interval(1400)
      .pipe(
        switchMap(() => this.api.getBuildJob(job.id)),
        takeWhile((response) => {
          const status = response.buildJob.status;
          this.currentBuildJob = response.buildJob;
          this.editorMessage =
            status === 'failed' && response.buildJob.error
              ? `构建失败：${response.buildJob.error}`
              : `构建状态：${buildStatusText(status)}`;
          if (!notify) {
            return !['success', 'failed', 'cancelled'].includes(status);
          }
          if (status === 'success') {
            this.showFeedback('构建完成', '静态页面已经更新。');
          } else if (status === 'failed') {
            this.showFeedback('构建失败', response.buildJob.error || '请查看构建日志。', 'error');
          } else if (status === 'cancelled') {
            this.showFeedback('构建已取消', '本次构建没有完成。', 'error');
          }
          return !['success', 'failed', 'cancelled'].includes(status);
        }, true),
        catchError((error) => {
          const message = errorMessage(error);
          this.editorMessage = message;
          this.showFeedback('构建状态获取失败', message, 'error');
          return of(null);
        })
      )
      .subscribe();
  }

  onTitleInput() {
    if (!this.currentPost && !this.draft.slug.trim()) {
      this.draft.slug = slugify(this.draft.title);
      this.draft.slugSource = 'title';
      this.draft.slugLocked = false;
    }
    this.updateAutosaveStatus();
  }

  onSlugInput() {
    this.draft.slugSource = 'manual';
    this.draft.slugLocked = true;
    this.updateAutosaveStatus();
  }

  hasUnsavedChanges() {
    return this.editorSnapshot() !== this.savedSnapshot;
  }

  @HostListener('window:beforeunload', ['$event'])
  beforeUnload(event: BeforeUnloadEvent) {
    if (this.hasUnsavedChanges()) {
      event.preventDefault();
      event.returnValue = '';
    }
  }

  @HostListener('window:keydown', ['$event'])
  handleKeydown(event: KeyboardEvent) {
    if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 's') {
      event.preventDefault();
      if (this.user && !this.saving) {
        void this.savePost();
      }
    }
  }

  onDraftInput() {
    this.updateAutosaveStatus();
  }

  statusText(status: PostStatus) {
    return statusText(status);
  }

  formatDate(value?: string | null) {
    if (!value) {
      return '无时间';
    }
    return new Intl.DateTimeFormat('zh-CN', {
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
      hour: '2-digit',
      minute: '2-digit'
    }).format(new Date(value));
  }

  tagText(post: Post) {
    return post.tags?.length ? post.tags.join(' / ') : '无标签';
  }

  buildStatusText(status: string) {
    return buildStatusText(status);
  }

  toggleBuildLog() {
    this.showBuildLog = !this.showBuildLog;
  }

  setEditorMode(mode: 'edit' | 'preview' | 'split') {
    this.editorMode = mode;
  }

  closeFeedback() {
    this.feedback = null;
    if (this.feedbackTimer) {
      clearTimeout(this.feedbackTimer);
      this.feedbackTimer = null;
    }
  }

  get previewHtml() {
    return renderMarkdown(this.draft.bodyMarkdown);
  }

  async generateSlug() {
    const title = this.draft.title.trim();
    if (!title) {
      this.showFeedback('无法生成 Slug', '请先填写标题。', 'error');
      return;
    }

    this.generatingSlug = true;
    this.editorMessage = '正在生成 Slug';
    try {
      const response = await firstValueFrom(
        this.api.generateSlug(title, this.draft.summary.trim(), this.currentPost?.id)
      );
      this.draft.slug = response.slug;
      this.draft.slugSource = 'ai';
      this.draft.slugLocked = false;
      this.editorMessage = 'Slug 已生成';
      this.showFeedback('Slug 已生成', response.slug);
      this.updateAutosaveStatus();
    } catch (error) {
      const message = errorMessage(error);
      this.editorMessage = message;
      this.showFeedback('Slug 生成失败', message, 'error');
    } finally {
      this.generatingSlug = false;
    }
  }

  async uploadImage(event: Event) {
    const input = event.target as HTMLInputElement;
    const file = input.files?.[0];
    if (!file) {
      return;
    }

    this.uploadingImage = true;
    this.editorMessage = '正在上传图片';
    try {
      const response = await firstValueFrom(this.api.uploadImage(file));
      this.insertMarkdown(response.image.markdown);
      this.editorMessage = '图片已上传并插入正文';
      this.showFeedback('图片上传成功', response.image.url);
      this.updateAutosaveStatus();
    } catch (error) {
      const message = errorMessage(error);
      this.editorMessage = message;
      this.showFeedback('图片上传失败', message, 'error');
    } finally {
      this.uploadingImage = false;
      input.value = '';
    }
  }

  get totalPages() {
    return Math.max(1, Math.ceil(this.totalPosts / this.pageSize));
  }

  get totalAuditPages() {
    return Math.max(1, Math.ceil(this.totalAuditLogs / this.auditPageSize));
  }

  get pageNumbers() {
    return Array.from({ length: this.totalPages }, (_, index) => index + 1);
  }

  actionText(action: string) {
    return actionText(action);
  }

  detailsText(log: AuditLog) {
    const details = log.details ?? {};
    const pairs = Object.entries(details)
      .filter(([, value]) => value !== undefined && value !== null && value !== '')
      .map(([key, value]) => `${key}: ${String(value)}`);
    return pairs.length ? pairs.join(' / ') : '无详情';
  }

  private async autosave() {
    if (!this.canAutosave()) {
      return;
    }
    await this.savePost(true);
  }

  private canAutosave() {
    const input = this.normalizedDraft();
    return Boolean(
      this.user &&
        !this.saving &&
        this.hasUnsavedChanges() &&
        input.title &&
        input.slug
    );
  }

  private normalizedDraft(): PostInput {
    return {
      ...this.draft,
      title: this.draft.title.trim(),
      slug: this.draft.slug.trim(),
      summary: this.draft.summary.trim(),
      cover: this.draft.cover.trim(),
      tags: parseTags(this.tagsText),
      createdAt: datetimeLocalToIso(this.draft.createdAt)
    };
  }

  private postToInput(post: Post): PostInput {
    return {
      slug: post.slug,
      title: post.title,
      summary: post.summary,
      bodyMarkdown: post.bodyMarkdown,
      status: post.status === 'deleted' ? 'draft' : post.status,
      tags: [...(post.tags ?? [])],
      cover: post.cover,
      slugSource: post.slugSource || 'manual',
      slugLocked: post.slugLocked,
      createdAt: toDateTimeLocal(post.createdAt)
    };
  }

  private emptyDraft(): PostInput {
    return {
      slug: '',
      title: '',
      summary: '',
      bodyMarkdown: '',
      status: 'draft',
      tags: [],
      cover: '',
      slugSource: 'manual',
      slugLocked: true,
      createdAt: toDateTimeLocal(new Date().toISOString())
    };
  }

  private confirmDiscard() {
    if (!this.hasUnsavedChanges()) {
      return true;
    }
    return window.confirm('当前文章有未保存的修改，确定要离开吗？');
  }

  private rememberSavedState() {
    this.savedSnapshot = this.editorSnapshot();
  }

  private updateAutosaveStatus() {
    if (this.saving) {
      return;
    }
    if (this.hasUnsavedChanges()) {
      this.autosaveStatus = '有未保存修改';
      return;
    }
    if (this.lastAutosavedAt) {
      this.autosaveStatus = `已自动保存 ${this.formatDate(this.lastAutosavedAt.toISOString())}`;
      return;
    }
    this.autosaveStatus = '已保存';
  }

  private editorSnapshot() {
    return JSON.stringify({
      ...this.draft,
      tags: parseTags(this.tagsText)
    });
  }

  private showFeedback(title: string, message: string, tone: FeedbackTone = 'success') {
    this.feedback = { title, message, tone };
    if (this.feedbackTimer) {
      clearTimeout(this.feedbackTimer);
    }
    this.feedbackTimer = setTimeout(() => {
      this.feedback = null;
      this.feedbackTimer = null;
    }, 3200);
  }

  private insertMarkdown(markdown: string) {
    const body = this.draft.bodyMarkdown.trimEnd();
    this.draft.bodyMarkdown = body ? `${body}\n\n${markdown}\n` : `${markdown}\n`;
  }
}

function normalizeTags(tags: string[]) {
  const seen = new Set<string>();
  return tags
    .map((tag) => tag.trim())
    .filter((tag) => {
      const key = tag.toLowerCase();
      if (!tag || seen.has(key)) {
        return false;
      }
      seen.add(key);
      return true;
    });
}

function parseTags(value: string) {
  return normalizeTags(value.split(/[,，]/));
}

function slugify(value: string) {
  return value
    .trim()
    .toLowerCase()
    .replace(/[\s_]+/g, '-')
    .replace(/[^a-z0-9\u4e00-\u9fa5-]+/g, '')
    .replace(/-+/g, '-')
    .replace(/^-|-$/g, '');
}

function toDateTimeLocal(value?: string | null) {
  if (!value) {
    return '';
  }
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) {
    return '';
  }
  const localDate = new Date(date.getTime() - date.getTimezoneOffset() * 60000);
  return localDate.toISOString().slice(0, 16);
}

function datetimeLocalToIso(value?: string | null) {
  if (!value) {
    return null;
  }
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) {
    return null;
  }
  return date.toISOString();
}

function renderMarkdown(markdown: string) {
  const lines = markdown.replace(/\r\n/g, '\n').split('\n');
  const html: string[] = [];
  let paragraph: string[] = [];
  let list: string[] = [];
  let code: string[] = [];
  let inCode = false;

  const flushParagraph = () => {
    if (paragraph.length === 0) {
      return;
    }
    html.push(`<p>${inlineMarkdown(paragraph.join(' '))}</p>`);
    paragraph = [];
  };

  const flushList = () => {
    if (list.length === 0) {
      return;
    }
    html.push(`<ul>${list.map((item) => `<li>${inlineMarkdown(item)}</li>`).join('')}</ul>`);
    list = [];
  };

  const flushCode = () => {
    html.push(`<pre><code>${escapeHtml(code.join('\n'))}</code></pre>`);
    code = [];
  };

  for (const line of lines) {
    if (line.trim().startsWith('```')) {
      if (inCode) {
        flushCode();
        inCode = false;
      } else {
        flushParagraph();
        flushList();
        inCode = true;
      }
      continue;
    }

    if (inCode) {
      code.push(line);
      continue;
    }

    const trimmed = line.trim();
    if (!trimmed) {
      flushParagraph();
      flushList();
      continue;
    }

    const heading = /^(#{1,6})\s+(.+)$/.exec(trimmed);
    if (heading) {
      flushParagraph();
      flushList();
      const level = heading[1].length;
      html.push(`<h${level}>${inlineMarkdown(heading[2])}</h${level}>`);
      continue;
    }

    if (/^[-*_]{3,}$/.test(trimmed)) {
      flushParagraph();
      flushList();
      html.push('<hr>');
      continue;
    }

    const listItem = /^[-*]\s+(.+)$/.exec(trimmed);
    if (listItem) {
      flushParagraph();
      list.push(listItem[1]);
      continue;
    }

    flushList();
    paragraph.push(trimmed);
  }

  if (inCode) {
    flushCode();
  }
  flushParagraph();
  flushList();

  return html.join('\n');
}

function inlineMarkdown(value: string) {
  return escapeHtml(value)
    .replace(/!\[([^\]]*)\]\(([^)\s]+)\)/g, '<img src="$2" alt="$1">')
    .replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>')
    .replace(/`([^`]+)`/g, '<code>$1</code>')
    .replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
    .replace(/\*([^*]+)\*/g, '<em>$1</em>');
}

function escapeHtml(value: string) {
  return value
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

function statusText(status: PostStatus) {
  return (
    {
      draft: '草稿',
      published: '已发布',
      archived: '归档',
      deleted: '已删除'
    } satisfies Record<PostStatus, string>
  )[status];
}

function buildStatusText(status: string) {
  return (
    {
      queued: '等待中',
      running: '构建中',
      success: '成功',
      failed: '失败',
      cancelled: '已取消'
    } as Record<string, string>
  )[status] ?? status;
}

function actionText(action: string) {
  return (
    {
      login: '登录',
      login_failed: '登录失败',
      logout: '退出',
      post_create: '新建文章',
      post_update: '修改文章',
      post_delete: '删除文章',
      post_publish: '发布文章',
      build_create: '提交构建',
      slug_generate: '生成 Slug'
    } as Record<string, string>
  )[action] ?? action;
}

function errorMessage(error: unknown) {
  if (typeof error === 'object' && error && 'error' in error) {
    const body = (error as { error?: { error?: string } }).error;
    if (body?.error) {
      return body.error;
    }
  }
  if (error instanceof Error) {
    return error.message;
  }
  return '请求失败';
}
