// Copyright 2022 Google LLC

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

//     http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.import { exec } from 'child_process';

import DOM from './helpers/DOMFuncs';
import { FileIO } from './helpers/FileIO';
import { loadMIgrationList } from './helpers/migrationList';
import { processQueriedEntries, TEvalExistingEntry } from './helpers/processXML';
import { repoPath } from './helpers/rootPath';
import { groupReplace } from './helpers/textFuncs';

async function init() {
    const migrationMap = await loadMIgrationList();
    const basePath = `${repoPath}/../tm-qpr-dev/frameworks/base/core/res/res/values/`;

    await processQueriedEntries(migrationMap, {
        containerQuery: 'declare-styleable[name="Theme"]',
        hidable: true,
        path: `${basePath}attrs.xml`,
        step: 0,
        tagName: 'attr',
        evalExistingEntry: (_attrValue, migItem, qItem) => {
            const { hidden, textContent: currentComment } = DOM.getElementComment(qItem);

            if (hidden) migItem.isHidden = hidden;

            const { newComment } = migItem;
            return [
                hidden ? 'update' : 'duplicate',
                {
                    attrs: { name: migItem.replaceToken },
                    ...(newComment
                        ? { comment: `${newComment} @hide ` }
                        : currentComment
                        ? { comment: hidden ? currentComment : `${currentComment} @hide ` }
                        : {}),
                },
            ];
        },
        evalMissingEntry: (_originalToken, { replaceToken, newComment }) => {
            return {
                tagName: 'attr',
                attrs: {
                    name: replaceToken,
                    format: 'color',
                },
                comment: `${newComment} @hide `,
            };
        },
    });

    // only update all existing entries
    await processQueriedEntries(migrationMap, {
        tagName: 'item',
        path: `${basePath}themes_device_defaults.xml`,
        containerQuery: 'resources',
        step: 2,
        evalExistingEntry: (_attrValue, { isHidden, replaceToken, step }, _qItem) => {
            if (step[0] != 'ignore')
                return [
                    isHidden ? 'update' : 'duplicate',
                    {
                        attrs: { name: replaceToken },
                    },
                ];
        },
    });

    // add missing entries on specific container
    await processQueriedEntries(migrationMap, {
        tagName: 'item',
        path: `${basePath}themes_device_defaults.xml`,
        containerQuery: 'resources style[parent="Theme.Material"]',
        step: 3,
        evalMissingEntry: (originalToken, { newDefaultValue, replaceToken }) => {
            return {
                tagName: 'item',
                content: newDefaultValue,
                attrs: {
                    name: replaceToken,
                },
            };
        },
    });

    const evalExistingEntry: TEvalExistingEntry = (_attrValue, { replaceToken, step }, _qItem) => {
        if (step[0] == 'update')
            return [
                'update',
                {
                    attrs: { name: replaceToken },
                },
            ];
    };

    await processQueriedEntries(migrationMap, {
        tagName: 'item',
        containerQuery: 'resources',
        path: `${basePath}../values-night/themes_device_defaults.xml`,
        step: 4,
        evalExistingEntry,
    });

    await processQueriedEntries(migrationMap, {
        tagName: 'java-symbol',
        path: `${basePath}symbols.xml`,
        containerQuery: 'resources',
        step: 5,
        evalExistingEntry,
    });

    // update attributes on tracked XML files
    {
        const searchAttrs = [
            'android:color',
            'android:indeterminateTint',
            'app:tint',
            'app:backgroundTint',
            'android:background',
            'android:tint',
            'android:drawableTint',
            'android:textColor',
            'android:fillColor',
            'android:startColor',
            'android:endColor',
            'name',
            'ns1:color',
        ];

        const filtered = new Map(
            [...migrationMap]
                .filter(([_originalToken, { step }]) => step[0] == 'update')
                .map(([originalToken, { replaceToken }]) => [originalToken, replaceToken])
        );

        const query =
            searchAttrs.map((str) => `*[${str}]`).join(',') +
            [...filtered.keys()].map((originalToken) => `item[name*="${originalToken}"]`).join(',');

        const trackedFiles = await FileIO.loadFileList(
            `${__dirname}/resources/whitelist/xmls1.json`
        );

        const promises = trackedFiles.map(async (locaFilePath) => {
            const filePath = `${repoPath}/${locaFilePath}`;

            const doc = await FileIO.loadXML(filePath);
            const docUpdated = DOM.replaceStringInAttributeValueOnQueried(
                doc.documentElement,
                query,
                searchAttrs,
                filtered
            );
            if (docUpdated) {
                await FileIO.saveFile(DOM.XMLDocToString(doc), filePath);
            } else {
                console.warn(`Failed to update tracked file: '${locaFilePath}'`);
            }
        });
        await Promise.all(promises);
    }

    // updates tag content on tracked files
    {
        const searchPrefixes = ['?android:attr/', '?androidprv:attr/'];
        const filtered = searchPrefixes
            .reduce<Array<[string, string]>>((acc, prefix) => {
                return [
                    ...acc,
                    ...[...migrationMap.entries()]
                        .filter(([_originalToken, { step }]) => step[0] == 'update')
                        .map(
                            ([originalToken, { replaceToken }]) =>
                                [`${prefix}${originalToken}`, `${prefix}${replaceToken}`] as [
                                    string,
                                    string
                                ]
                        ),
                ];
            }, [])
            .sort((a, b) => b[0].length - a[0].length);

        const trackedFiles = await FileIO.loadFileList(
            `${__dirname}/resources/whitelist/xmls2.json`
        );

        const promises = trackedFiles.map(async (locaFilePath) => {
            const filePath = `${repoPath}/${locaFilePath}`;
            const doc = await FileIO.loadXML(filePath);
            const docUpdated = DOM.replaceContentTextOnQueried(
                doc.documentElement,
                'item, color',
                filtered
            );
            if (docUpdated) {
                await FileIO.saveFile(DOM.XMLDocToString(doc), filePath);
            } else {
                console.warn(`Failed to update tracked file: '${locaFilePath}'`);
            }
        });
        await Promise.all(promises);
    }

    // replace imports on Java / Kotlin
    {
        const replaceMap = new Map(
            [...migrationMap.entries()]
                .filter(([_originalToken, { step }]) => step[0] == 'update')
                .map(
                    ([originalToken, { replaceToken }]) =>
                        [originalToken, replaceToken] as [string, string]
                )
                .sort((a, b) => b[0].length - a[0].length)
        );

        const trackedFiles = await FileIO.loadFileList(
            `${__dirname}/resources/whitelist/java.json`
        );

        const promises = trackedFiles.map(async (locaFilePath) => {
            const filePath = `${repoPath}/${locaFilePath}`;
            const fileContent = await FileIO.loadFileAsText(filePath);
            const str = groupReplace(fileContent, replaceMap, 'R.attr.(#group#)(?![a-zA-Z])');
            await FileIO.saveFile(str, filePath);
        });
        await Promise.all(promises);
    }
}

init();