// Silence may be golden, but sometimes words paint the canvas of understanding.
const BrokenLinksFilter = {
    name: 'BrokenLinksFilter',
    props: {
        filter: {
            type: Object,
            required: true,
            default: {},
        }
	},
    components: {
        LinksyForm,
        LinksyDropdown,
        LinksyButton
    },
    data() {
        return {
            statusCodes: [200, 202, 300, 310, 403, 404, 460, 500]
        }
    },
    methods: {
        handleFilterChange(e) {
            this.$emit("onChange", { ...e });
        },
        reset() {
            let filterable = {
                status_code: null
            }
            
            this.$emit("onChange", filterable);
        },
    },
    template: `
        <linksy-form
            enableReinitialize
            class="linksy-filters-box rounded-3"
            :initial-values="filter"
            :onSubmit="handleFilterChange"
            v-slot="{
                values,
                errors,
                handleChange,
                handleSubmit,
                setFieldValue,
            }"
        >
            <div class="d-flex flex-row align-items-start justify-content-between gap-4">
                <div class="linksy-filter-option-box">
                    <span>Status Code</span>
                    <linksy-dropdown
                        is-multi
                        is-resetable
                        :options="statusCodes"
                        placeholder="All"
                        :initial-value="values.status_code"
                        @selected="(e) => setFieldValue('status_code', e, true)"
                    >
                    </linksy-dropdown>
                </div>
                

                <div class="linksy-filter-actions d-flex align-items-end justify-content-end gap-2 text-nowrap">
                    <div class="playground-search">
                        <linksy-button
                            variant="primary_outline"
                            @click="handleSubmit"
                        >
                            <i class="fa fa-check me-2"></i>
                            <span>Apply</span>
                        </linksy-button>
                    </div>
                    <linksy-button variant="secondary" @click="reset">
                        <i class="fa fa-times me-2"></i>
                        <span>Reset</span>
                    </linksy-button>
                </div>
            </div>
        </linksy-form>
    `
}

const BrokenLinks = {
	name: 'BrokenLinks',
    directives: {
		'linksy-tooltip': LinksyTooltip,
        'linksy-dom-text': LinksyDOMText
	},
	components: {
		LinksyDataTable,
		LinksyPagination,
        LinksySearch,
        LinksyDropdown,
        LinksyButton,
        BrokenLinksFilter
	},
	data() {
		return {
            columns: [
                {
                    name: 'URL'
                },
                {
                    name: 'Source'
                },
                {
                    name: 'Anchor'
                },
                {
                    name: 'Status'
                }
            ],
            search: '',
            urlReplacement: '',
            anchorReplacement: '',
            showNotice: false,
            isProcessing: false,
            failedToProcess: false,
            isReplacing: false,
			searchPlaceholder: 'Enter url to search',
			isLoading: false,
            isRedirect: false,
			selectedSearchType: 'ALL',
			selectedAction: null,
			selectedOption: null,
			filterSearch: '',
            selectedItems: [],
			replacer: new LinksyBrokenLinks(LINKSY_SECURE_TOKEN),
            isdetailsContainerOpened: false,
            activeHeight: 0,
            filter: {
                status_code: null
            },
            showFilter: false
		}
	},
	computed: {
        linksData: {
            get() {
                return this.replacer.data
            }
        },
        actions: {
			get() {
                switch (this.selectedSearchType) {
                    case 'IGNORED':
                        return LinksyBrokenLinks.SUPPORTED_ACTIONS.filter(
                            (a) => !['fix_redirect', 'ignore'].includes(a.value)
                        );
                    case 'REDIRECTS':
                        return LinksyBrokenLinks.SUPPORTED_ACTIONS.filter(
                            (a) => a.value !== 'undo_ignore'
                        );
                    default:
                        return LinksyBrokenLinks.SUPPORTED_ACTIONS.filter(
                            (a) => !['fix_redirect', 'undo_ignore'].includes(a.value)
                        );
                }
			},
		},
        linkOptions: {
            get() {
                if (['TIMEOUT', 'BROKEN', 'WARNING'].includes(this.selectedSearchType)) {
                    return LinksyBrokenLinks.SUPPORTED_OPTIONS.filter(
                        (a) => !['fix_redirect', 'undo_ignore'].includes(a.value)
                    )
                } else if (this.selectedSearchType !== 'IGNORED') {
                    return LinksyBrokenLinks.SUPPORTED_OPTIONS.filter(
                        (a) => a.value !== 'undo_ignore'
                    )
                } else if (this.selectedSearchType === 'IGNORED') {
                    return LinksyBrokenLinks.SUPPORTED_OPTIONS.filter(
                        (a) => a.value !== 'ignore'
                    )
                }
            }
        },
        httpTextArray: {
			get() {
				return LinksyBrokenLinks.HTTP_TEXT_ARRAY
			},
		},
		searchTypes: {
			get() {
				return [
                    {
                        value: 'ALL',
                        label: 'All',
                        count: this.linksData?.total_links,
                    },
                    {
                        value: 'BROKEN',
                        label: 'Broken',
                        count: this.linksData?.total_broken,
                    },
                    {
                        value: 'WARNING',
                        label: 'Warning',
                        count: this.linksData?.total_warning,
                    },
                    {
                        value: 'REDIRECTS',
                        label: 'Redirects',
                        count: this.linksData?.total_redirects,
                    },
                    {
                        value: 'TIMEOUT',
                        label: 'Timeout',
                        count: this.linksData?.total_timeout,
                    },
                    {
                        value: 'IGNORED',
                        label: 'Ignored',
                        count: this.linksData?.total_ignored,
                    }
                ]
			},
		},
        items: {
			get() {
				return this.linksData?.results ?? []
			},
		},
		pagination: {
			get() {
				return this.replacer.pagination
			},
		},
        pageLength: {
			get() {
				return Object.keys(this.items).length
			},
		},
        isAllRowsSelected: {
			get() {
				return this.items.length && this.selectedItems?.length === this.items.length
			},
		},
        isNonReloadOption: {
            get() {
                return ['unlink', 'recheck', 'ignore', 'undo_ignore'].includes(this.selectedOption);
            }
        },
        isFindNReplace: {
            get() {
                return this.selectedAction === 'replace'
            }
        },
        isApplyDisabled: {
            get() {
                return this.selectedAction === null || this.isLoading || this.isFindNReplace || this.isProcessing
            }
        }
	},
	watch: {
        isdetailsContainerOpened: {
			handler: function (val) {
                setTimeout(() => {
                    if (val && this.$refs?.detailsContainer?.clientHeight) {
                        this.activeHeight = this.$refs?.detailsContainer?.clientHeight
                    }
                }, 20);
			},
		},
    },
	methods: {
        handleFilterChange(values) {
			this.filter = { ...values }
			this.replacer.reset()

			this.getBrokenLinks()
		},
        toggleFilter() {
            this.showFilter = !this.showFilter
        },
        handleSearch(q) {
			if (this.search !== q) {
				this.search = q
			}

            this.getBrokenLinks()
		},
        getValidOptions(item) {
            const httpCode = item.status_code;

            if (httpCode >= 200 && httpCode < 300 && item.final_url !== item.url) {
                return this.linkOptions
            }
            
            return this.linkOptions.filter(e => e.value !== 'fix_redirect')
        },
        selectAction(index) {
			let action = Object.values(this.actions)[index]

			if (action.value === this.selectedAction) {
				this.selectedAction = null
			} else {
				this.selectedAction = action?.value
			}
		},
        httpCodeToColor(httpCode) {
            switch (true) {
                case (httpCode >= 100 && httpCode < 200):
                    return '#007bff';
                case (httpCode >= 200 && httpCode < 300):
                    return '#28a745';
                case (httpCode >= 300 && httpCode < 400):
                    return '#ffc107';
                case (httpCode >= 400 && httpCode < 500):
                    return '#dc3545';
                case (httpCode >= 500 && httpCode < 600):
                    return '#343a40';
                default:
                    return '#000000';
            }
        },
        handleHttpText(code) {
            const range = Math.floor(code / 100);
            let message = '';

            switch (range) {
                case 1:
                    message = 'Informational Response';
                    break;
                case 2:
                    message = 'OK';
                    break;
                case 3:
                    message = 'Temporary Redirect' || 'Timeout';
                    break;
                case 4:
                    message = 'Client Error';
                    break;
                case 5:
                    message = 'Server Error';
                    break;
            }

            const type = this.httpTextArray.find((type) => type.code === code)
			if (type) {
                return type.text
			}

            return message
        },
        httpCodeToIcon(httpCode) {
            switch (true) {
                case (httpCode >= 100 && httpCode < 200): // Informational
                    return 'fa-solid fa-info-circle';
                case (httpCode >= 200 && httpCode < 300): // Success
                    return 'fa-solid fa-circle-check';
                case (httpCode >= 300 && httpCode < 400): // Redirection
                    return 'fa-solid fa-arrow-right';
                case (httpCode >= 400 && httpCode < 500): // Client Errors
                    return 'fa-solid fa-triangle-exclamation';
                case (httpCode >= 500 && httpCode < 600): // Server Errors
                    return 'fa-solid fa-server';
                default:
                    return 'fa-solid fa-question-circle';
            }
        },
        selectAll() {
			this.selectedItems = this.selectedItems.length === this.items.length ? [] : [...this.items]
		},
        handleSearchType(value) {
            const type = this.searchTypes.find((type) => type.value === value)
			if (type) {
				this.selectedSearchType = type.value

                this.replacer.toPage(0)
                this.selectedItems = []
                this.getBrokenLinks()
			}
        },
        selectRow(row) {
			const idx = this.selectedItems.findIndex((e) => e.url === row.url && e.anchor === row.anchor && e.post_id === row.post_id)

			if (idx !== -1) {
				this.selectedItems.splice(idx, 1)
			} else {
				this.selectedItems.push(row)
			}
		},
        isRowSelected(row) {
			const idx = this.selectedItems.findIndex((item) => item.url === row.url && item.anchor === row.anchor && item.post_id === row.post_id)

			return idx > -1
		},
        handleLink(item, value) {
            if (['recheck', 'ignore', 'undo_ignore'].includes(value)) {
                this.selectedOption = value
                this.applySingleOption(item)
            }

            switch (value) {
                case 'details':
                    item.opened = !item.opened
                    this.isdetailsContainerOpened = !this.isdetailsContainerOpened
                    break;
                case 'fix_redirect':
                    this.toggleEdit(item)
                    this.isRedirect = true
                    this.selectedOption = 'replace'
                    break;
                case 'unlink':
                    this.selectedOption = value

                    if (window.confirm(`Are you sure you want to unlink the URL??`)) {
                        this.applySingleOption(item);
                    }
                    break;
                case 'edit_url':
                    this.toggleEdit(item)
                    this.selectedOption = 'replace'
                    break;
            }
        },
        handleCancel() {
			this.selectedAction = null
		},
        formatDate(date) {
            return moment(date).format('MMMM DD, YYYY')
        },
        async getBrokenLinks() {
            try {
                this.isLoading = true

				await this.replacer.searchBrokenLinks(this.search, this.selectedSearchType, this.filterSearch, this.filter)
			} catch (error) {
				this.$toast.error(error.response?.data?.data || error.message)
			} finally {
				this.isLoading = false
			}
        },
        handleChangePage(page) {
			this.replacer.toPage(page - 1)
			this.getBrokenLinks()
		},
		handleChangeRowsPerPage(limit) {
			if (limit != this.pagination.limit) {
				this.replacer.pagination = { limit }
				this.getBrokenLinks()
			}
		},
        toggleEdit(item) {
            item.opened = false;
            item.editing = !item.editing;
            this.urlReplacement = item.url;
            this.anchorReplacement = item.anchor;
        },
        cancelEdit(item) {
            item.editing = false;
            this.isRedirect = false;
            this.urlReplacement = '';
            this.selectedOption = null
        },
        refreshTable() {
			this.showNotice = false
			this.failedToProcess = false

            this.getBrokenLinks()
		},

        updateBrokenLinks(item, data, option, useFinalUrl) {
            let message = 'Link processed successfully'

            if (data['failed'].length > 0) {
                this.$toast.error('Failed to process link')
                return;
            }

            if (option === 'unlink') {
                if (data['deleted'] && data['deleted'].length > 0) {
                    const index = this.items.findIndex(e => e.id === item.id);
                    if (index !== -1) {
                        this.items.splice(index, 1);
                    }

                    this.getBrokenLinks()
                }
            } else if (option === 'recheck') {
                const link = data.replaced[0]
                const index = this.items.findIndex(e => e.url === link.url && e.anchor === link.anchor);
                if (index !== -1) {
                    this.items[index] = { ...this.items[index], ...link };
                }
            } else if (option === 'replace') {
                if (data['replaced'] && data['replaced'].length > 0) {
                    item.editing = !item.editing;
                    item.url = useFinalUrl ? item.final_url : this.urlReplacement
                }
            } else if (['ignore', 'undo_ignore'].includes(option)) {
                this.getBrokenLinks()
            }

            this.$toast.success(message)
        },
        
        async applySingleOption(item, useFinalUrl = false) {
            if (this.isNonReloadOption) {
                this.isLoading = true
            } else {
                this.isReplacing = true
            }
            
            let replacement = this.urlReplacement
            if (useFinalUrl) {
                replacement = item.final_url
            }

            if (this.selectedOption == 'replace') {
                item.anchor = this.anchorReplacement
            }

            try {
                const data = await this.replacer.applySelectedOption(
                    this.selectedOption,
                    item,
                    replacement
                )

                this.updateBrokenLinks(item, data, this.selectedOption, useFinalUrl);
                 
            } catch (error) {
                this.$toast.error(error.response?.data?.data || error.message)
            } finally {
                if (this.isNonReloadOption) {
                    this.isLoading = false
                } else {
                    this.isReplacing = false
                }
            }
        },
        applyAction() {
			if (this.selectedItems.length === 0) {
				this.$toast.error('No link to process')
				return
			} else {
				this.isLoading = true

				setTimeout(() => {
					this.isLoading = false
					this.failedToProcess = false
					this.$toast.info('Links will be processed in the background')
					this.applyBulkAction()
				}, 1500);
			}
		},
        async applyBulkAction() {
            this.showNotice = true
            this.isProcessing = true

            try {
                const report = await this.replacer.applyBulkAction(
                    this.selectedAction,
                    this.selectedItems,
                    this.urlReplacement
                )

                let message = ''
                const actions = {
                    replace: 'replaced',
                    recheck: 'rechecked',
                    fix_redirect: 'fixed',
                    unlink: 'unlinked'
                }

                message = `${this.selectedItems.length - report.failed.length} of ${this.selectedItems.length} link(s) ${actions[this.selectedAction]}`
            
                if (report.failed.length > 0 && report.failed.length === this.selectedItems.length) {
					this.$toast.error(message);
					this.failedToProcess = true;
				} else {
					this.$toast.success(message);
					this.selectedItems = [];
                    this.selectedAction = null
				}
            } catch (error) {
                this.$toast.error(error.response?.data?.data || error.message)
            } finally {
                this.isProcessing = false
            }
        }
	},
	template: `
        <div id="broken-links" class="d-flex flex-column gap-4">

            <div v-if="showNotice" class="notice d-flex justify-content-between align-items-center border-success m-0 mb-3 p-2" :class="['border-' + (failedToProcess ? 'danger' : 'success')]">
				<span v-if="isProcessing">Processing Links... <cite>Do not refresh page.</cite></span>
				<div v-else>
					<span v-if="!failedToProcess">Links have been processed successfully. <span class="text-decoration-underline text-primary cursor-pointer" @click="refreshTable">Refresh Links</span></span>
					<span v-else>Failed to process links.</span>
				</div>
				<i class="fa fa-xmark fs-6 text-danger cursor-pointer" @click="() => showNotice = false"></i>
			</div>

            <div class="d-flex flex-column gap-3">
                <div class="d-flex align-items-center justify-content-between">
                    <div class="d-flex action-dropdown gap-3">
                        <linksy-dropdown
                            is-manual
                            is-async
                            class="bg-white text-dark border border-1 rounded-3"
                            :initial-value="actions.find(e => e.value == selectedAction)?.label"
                            :options="actions"
                        >
                            <template #title>
                                <span class="text-nowrap">Bulk action</span>
                            </template>
                            <template #item="{ item, index, selected }">
                                <div class="d-flex w-100 gap-1 align-items-center" @click="selectAction(index)">
                                    <div class="linksy-checkbox" :class="{'checked': this.selectedAction === item.value}" />
                                    <label class="cursor-pointer">{{ item.label }}</label>
                                    <span class="ms-auto text-wrap" v-linksy-tooltip="{ message: actions[index]?.message, flow: 'right' }"></span>
                                </div>
                            </template>
                            <template #footer="props">
                                <div class="actions-button d-flex gap-2 mt-2">
                                    <linksy-button class="flex-grow-1" :disabled="isApplyDisabled" @click="applyAction">Apply</linksy-button>
                                    <linksy-button variant="secondary" class=" flex-grow-1" @click="handleCancel">Cancel</linksy-button>
                                </div>
                            </template>
                        </linksy-dropdown>

                        <linksy-search
                            cancelable
                            class="d-flex flex-fill flex-grow-1 border border-1 rounded-3"
                            :placeholder="searchPlaceholder"
                            :initial-value="searchInitialValue"
                            @on-change="handleSearch"
                        >
                        </linksy-search>
                    </div>

                    <linksy-button variant="white_outline" class="filter-btn-toggle text-nowrap d-flex align-items-center" :class="{active: showFilter}" @click="toggleFilter">
                        <i class="fa-solid fa-bars-staggered me-2"></i><span>Filter</span>
                    </linksy-button>
                </div>

                <broken-links-filter
                    v-if="showFilter"
                    :filter="filter"
                    @on-change="handleFilterChange"
                >
                </broken-links-filter>

                <div class="linksy-tabbar tabbar broken-links-tabbar">
                    <ul class="d-flex gap-2 m-0">
                        <li v-for="item in searchTypes"
                            :key="item.value"
                            class="search-option-item d-flex align-items-center justify-content-center gap-2"
                            :class="{'active': selectedSearchType === item.value }"
                            @click="() => handleSearchType(item.value)"
                        >
                            <label
                                class="cursor-pointer"
                                :for="item"
                            >
                                {{ item.label }}
                            </label><span class="count">{{ item.count || 0 }}</span>
                        </li>
                    </ul>
                </div>
            </div>

            <div v-if="isFindNReplace">
                <hr>
                <div class="find-and-replace">
                    <strong class="fs-6">Bulk Replace URLs</strong>
                    <div class="d-flex flex-column gap-2 mt-2">
                        <input
                            class="form-input"
                            type="text"
                            placeholder="Replace all selected links with a single link"
                            v-model="urlReplacement"
                        />
                    </div>
                    <div class="d-flex justify-content-end gap-2 mt-2">
                        <linksy-button class="shadow-sm" variant="white" @click="() =>{ selectedAction = null; urlReplacement = ''}">
                            Cancel
                        </linksy-button>
                        <linksy-button class="shadow-sm" :loading="isReplacing" @click="applyAction">
                            Apply
                        </linksy-button>
                    </div>
                </div>
			</div>

            <linksy-data-table
				class="shadow-sm"
                :columns="columns"
                :data="items"
                :loading="isLoading"
                @onSort=""
            >
                <template v-slot:column-header-0>
                    <div class="d-flex align-items-center">
                        <div
                            class="linksy-checkbox me-4"
                            :class="{'checked': isAllRowsSelected, 'is-disabled': isProcessing || isLoading }"
                            @click="() => selectAll()"
                        ></div>
                        URL<span class="fs-20px linksy-item-count ms-1" v-if="!isLoading">{{linksData.total}}</span>
                    </div>
                </template>
            
                <template v-slot:column-0="{item, index}">
                    <div class="d-flex gap-4">
                        <div
                            class="linksy-checkbox mt-2"
                            @click="() => selectRow(item)"
                            :class="{'checked': isRowSelected(item), 'is-disabled': isProcessing || isLoading }"
                        ></div>
                        <form v-if="item.editing" class="url-edit-form w-100">
                            <div v-if="item.final_url !== item.url && isRedirect" class="fs-6 mt-2">
                                <strong>URL:</strong> {{ item.url }}
                                <div class="mb-2">
                                    <strong>Redirect URL:</strong> <br/>
                                    <a class="text-wrap" :href="item.final_url" target="_blank">{{ item.final_url }}</a>
                                </div>
                                <div class="d-flex gap-2">
                                    <linksy-button :loading="isReplacing" @click="applySingleOption(item, true)">
                                        Fix Redirect
                                    </linksy-button>
                                    <linksy-button class="shadow-sm" variant="white" @click="cancelEdit(item)">
                                        Cancel
                                    </linksy-button>
                                </div>
                            </div>
                            <div v-else>
                                <div class="d-flex gap-3">
                                    <label class="fs-6">Anchor:</label>
                                    <input
                                        type="text"
                                        v-model="anchorReplacement"
                                        class="form-control w-100"
                                    />
                                </div>
                                <div class="d-flex gap-3 mt-2">
                                    <label class="fs-6">URL:</label>
                                    <input
                                        type="text"
                                        v-model="urlReplacement"
                                        class="form-control w-100"
                                    />
                                </div>
                                <div class="edit-button d-flex justify-content-end gap-2 mt-3">
                                    <linksy-button class="shadow-sm" :loading="isReplacing" @click="applySingleOption(item)">
                                        <i class="fa fa-check"></i>
                                    </linksy-button>
                                    <linksy-button class="shadow-sm" variant="white" @click="cancelEdit(item)">
                                        <i class="fa fa-times"></i>
                                    </linksy-button>
                                </div>
                            </div>
                        </form>
                        
                        <div v-else class="url-container position-relative">
                            <a class="item-url text-break" :href="item.url" target="_blank">{{ item.url }}</a>
                            <div class="item-links align-items-center gap-2 m-0">
                                <li
                                    v-for="opts in getValidOptions(item)"
                                    :key="opts.value"
                                    :class="opts.value"
                                    class="link-option-item d-flex align-items-center m-0 text-truncate cursor-pointer"
                                    @click="handleLink(item, opts.value)"
                                >
                                    {{ opts.label }}
                                </li>
                            </div>
                        </div>
                    </div>
                    <div v-if="item.opened" class="details ms-5" :style="{'min-height': (activeHeight + 15)+'px', 'margin-top': '1rem'}">
                        <div ref="detailsContainer" class="details-container fs-6">
                            <ul>
                                <li><strong>Link last checked:</strong> {{formatDate(item.last_checked)}}</li>
                                <li><strong>HTTP code:</strong> {{item.status_code}} </li>
                                <li><strong>Response time:</strong> {{item.response_time}} seconds</li>
                                <li><strong>Final URL:</strong> <a v-if="item.final_url" :href="item.final_url" target="_blank">{{item.final_url}}</a> <span v-else>No redirect</span></li>
                                <br />
                                <li>This link has been checked {{item.check_count > 1 ? item.check_count + ' times' : 'once'}}</li>
                            </ul>
                        </div>
                    </div>
                </template>

                <template v-slot:column-1="{item, index}">
                    <div class="url-container position-relative">
                        <span>{{ item.source }}</span>
                        <ul class="item-links translate-top align-items-center gap-2 m-0">
                            <li class="link-option-item d-flex align-items-center m-0 cursor-pointer">
                                <a class="fw-normal text-black text-decoration-none" :href="item.edit_url" target="_blank">Edit</a>
                            </li>
                            <li class="link-option-item d-flex align-items-center m-0 cursor-pointer">
                                <a class="fw-normal text-black text-decoration-none" :href="item.view_url" target="_blank">View</a>
                            </li>
                        </ul>
                    </div>
                </template>

                <template v-slot:column-2="{item, index}">
                    <span class="fw-normal text-wrap" v-linksy-dom-text>{{item.anchor}}</span>
                </template>

                <template v-slot:column-3="{item, index}">
                    <div class="http-status fs-14px d-flex align-items-center rounded-3 py-1 px-2" :style="{color: httpCodeToColor(item.status_code)}">
                        <div v-if="item.status_code" class="d-flex align-items-center gap-1" :title="item.status_code + ' ' + handleHttpText(item.status_code)">
                            <i :class="httpCodeToIcon(item.status_code)"></i>
                            <span>{{ item.status_code }}</span>
                        </div>
                        <div v-else class="d-flex align-items-center text-muted">
                            <i class="fa-solid fa-rotate-right"></i>
                            <cite>heck</cite>
                        </div>
                    </div>
                </template>
            </linksy-data-table>

            <linksy-pagination
                :pageLength="pageLength"
                :perPage="pagination.limit"
                :currentPage="pagination.page+1"
                :total="pagination.total"
                @on-change-page="handleChangePage"
                @on-change-rows-per-page="handleChangeRowsPerPage"
            >
            </linksy-pagination>
        </div>
    `,
    mounted() {
        this.getBrokenLinks()
    }
}
