import SwiftUI

private struct ButtonAction {

    let name: String

    let action: () -> Void
}

private struct PopupSheet: View {

    @Binding
    var isPresented: Bool

    @Binding
    var title: String

    @Binding
    var message: String

    var body: some View {
        VStack {
            Text(title)
                .font(.headline)
            Text(message)
            Button("Dismiss") {
                message = ""
                isPresented = false
            }
        }.padding()
    }
}

private struct PageIssueGenericView: View {

    let issue: PageIssue

    let buttons: [ButtonAction]

    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text(issue.message.description)
                Text("\(issue.title) (\(issue.language.rawValue.uppercased()))")
                    .font(.caption)
            }
            Spacer()
            ForEach(buttons, id: \.name) { button in
                Button(button.name, action: button.action)
            }
        }
    }
}

struct PageIssueView: View {

    let issue: PageIssue

    @EnvironmentObject
    private var checker: PageIssueChecker

    @EnvironmentObject
    private var content: Content

    @State
    private var showPopupMessage = false


    @State
    private var popupTitle = "Error"

    @State
    private var popupMessage = ""

    @State
    private var showPagePicker = false

    @State
    private var selectedPage: Page?

    @State
    private var showFilePicker = false

    @State
    private var selectedFile: FileResource?

    private var buttons: [ButtonAction] {
        switch issue.message {
        case .warning:
            return [.init(name: "Retry", action: retryPageCheck)]
        case .failedToLoadContent:
            return [.init(name: "Retry", action: retryPageCheck)]
        case .failedToParseContent:
            return [.init(name: "Retry", action: retryPageCheck)]
        case .missingFile(let missing, _):
            return [
                .init(name: "Select file", action: { selectFile(missingFile: missing) }),
                .init(name: "Create external file", action: { createExternalFile(fileId: missing) })
            ]
        case .missingPage(let missing, _):
            return [
                .init(name: "Select page", action: selectPage),
                .init(name: "Create page", action: { createPage(pageId: missing) })
            ]
        case .missingTag(let missing, _):
            return [
                .init(name: "Select tag", action: { selectTag(missingPage: missing) }),
                .init(name: "Create tag", action: { createTag(tagId: missing) })
            ]
        case .invalidCommand(_, let markdown):
            return [.init(name: "Replace text", action: { replaceCommand(originalText: markdown) })]
        }
    }

    var body: some View {
        PageIssueGenericView(issue: issue, buttons: buttons)
            .sheet(isPresented: $showPopupMessage) {
                PopupSheet(isPresented: $showPopupMessage, title: $popupTitle, message: $popupMessage)
            }
            .sheet(isPresented: $showPagePicker) {
                if let page = selectedPage {
                    didSelect(page: page)
                }
            } content: {
                PagePickerView(selectedPage: $selectedPage)
            }
            .sheet(isPresented: $showFilePicker) {
                if let file = selectedFile {
                    didSelect(file: file)
                }
            } content: {
                FileSelectionView(selectedFile: $selectedFile)
            }

    }

    private func show(error: String) {
        DispatchQueue.main.async {
            self.popupTitle = "Error"
            self.popupMessage = error
            self.showPopupMessage = true
        }
    }

    private func show(info: String) {
        DispatchQueue.main.async {
            self.popupTitle = "Info"
            self.popupMessage = info
            self.showPopupMessage = true
        }
    }


    private func retryPageCheck() {
        DispatchQueue.main.async {
            checker.check(pages: content.pages, clearListBeforeStart: false)
        }
    }

    private func selectFile(missingFile: String) {
        selectedFile = nil
        showFilePicker = true
    }

    private func didSelect(file newFile: FileResource) {
        guard case .missingFile(let missingFile, let markdown) = issue.message else {
            show(error: "Inconsistency: Selected file, but issue is not a missing file")
            return
        }
        replace(missing: missingFile, with: newFile.id, in: markdown)
        retryPageCheck()
        DispatchQueue.main.async {
            selectedFile = nil
        }
    }

    private func createExternalFile(fileId: String) {
        guard content.isValidIdForFile(fileId) else {
            show(error: "Invalid file id, can't create external file")
            return
        }

        let file = FileResource(
            content: content,
            id: fileId,
            isExternallyStored: true,
            en: "",
            de: "")
        content.add(file)

        retryPageCheck()
    }

    private func selectPage() {
        selectedPage = nil
        showPagePicker = true
    }

    private func didSelect(page newPage: Page) {
        guard case .missingPage(let missingPage, let markdown) = issue.message else {
            show(error: "Inconsistency: Selected page, but issue is not a missing page")
            return
        }

        replace(missing: missingPage, with: newPage.id, in: markdown)
        retryPageCheck()
        DispatchQueue.main.async {
            selectedPage = nil
        }
    }

    private func createPage(pageId: String) {
        guard content.isValidIdForTagOrPageOrPost(pageId) else {
            show(error: "Invalid page id, can't create page")
            return
        }

        let deString = pageId + "-" + ContentLanguage.german.rawValue

        let page = Page(
            content: content,
            id: pageId,
            externalLink: nil,
            isDraft: true,
            createdDate: .now,
            hideDate: false,
            startDate: .now,
            endDate: nil,
            german: .init(content: content,
                          urlString: deString,
                          title: pageId),
            english: .init(content: content,
                           urlString: pageId,
                           title: pageId),
            tags: [],
            requiredFiles: [])
        content.pages.insert(page, at: 0)

        retryPageCheck()
    }

    private func selectTag(missingPage: String) {
        // TODO: Show sheet to select a tag
        // TODO: Replace tag id in page content with new tag id

        retryPageCheck()
    }

    private func createTag(tagId: String) {
        guard content.isValidIdForTagOrPageOrPost(tagId) else {
            show(error: "Invalid tag id, can't create tag")
            return
        }

        let tag = Tag(content: content, id: tagId)
        content.tags.append(tag)

        retryPageCheck()
    }

    private func replaceCommand(originalText: String) {
        // TODO: Show sheet with text input
        // TODO: Replace original text in page content with new text

        retryPageCheck()
    }

    // MARK: Page Content manipulation

    private func replace(missing: String, with newText: String, in markdown: String) {

        let newString = markdown.replacingOccurrences(of: missing, with: newText)
        guard newString != markdown else {
            show(error: "No change in content detected trying to perform replacement")
            return
        }

        let occurrences = findOccurrences(of: markdown, in: issue.page, language: issue.language)
        guard !occurrences.isEmpty else {
            show(error: "No occurrences of '\(markdown)' found in the page")
            return
        }
        replace(markdown, with: newString, in: issue.page, language: issue.language)

        show(info: "Replaced \(occurrences.count) occurrences of '\(missing)' with '\(newText)'")

        retryPageCheck()
    }

    private func replace(_ oldString: String, with newString: String, in page: Page, language: ContentLanguage) {
        guard let pageContent = content.storage.pageContent(for: page.id, language: language) else {
            print("Failed to replace in page \(page.id) (\(language)), no content")
            return
        }
        let modified = pageContent.replacingOccurrences(of: oldString, with: newString)

        guard content.storage.save(pageContent: modified, for: page.id, language: language) else {
            print("Replaced \(oldString) with \(newString) in page \(page.id) (\(language))")
            return
        }
    }

    private func findOccurrences(of searchString: String, in page: Page, language: ContentLanguage) -> [String] {
        guard let parts = content.storage.pageContent(for: page.id, language: language)?
            .components(separatedBy: searchString) else {
            print("Failed to get page content to find occurrences, no content")
            return []
        }

        var occurrences: [String] = []
        for index in parts.indices.dropLast() {
            let start = parts[index].suffix(10)
            let end = parts[index+1].prefix(10)
            let full = "...\(start)\(searchString)\(end)...".replacingOccurrences(of: "\n", with: "\\n")
            occurrences.append(full)
        }
        return occurrences
    }
}
