# -*- coding: utf-8 -*-
# Copyright 2020 Google Inc.
#
# 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.

"""Defines the feature launch processes, stages, progress items, and rules for required approvals and fields."""

from dataclasses import asdict, dataclass

from internals import approval_defs, core_enums
from internals.core_models import Stage
from internals.metrics_models import WebDXFeatureObserver
from internals.review_models import Gate


@dataclass
class Action:
    """Dataclass for Action."""

    name: str
    url: str
    prerequisites: list[str]


@dataclass
class ProgressItem:
    """Dataclass for ProgressItem."""

    name: str
    field: str | None = None


# Note: A new feature always starts with intent_stage == INTENT_NONE
# regardless of process.  intent_stage is set to the first stage of
# a specific process when the user clicks a "Start" button and submits
# a form that sets intent_stage.
@dataclass
class ProcessStage:
    """Dataclass for ProcessStage."""

    name: str
    description: str
    progress_items: list[ProgressItem]
    actions: list[Action]
    approvals: list[approval_defs.GateInfo]
    incoming_stage: int
    outgoing_stage: int
    stage_type: int | None


@dataclass
class Process:
    """Dataclass for Process."""

    name: str
    description: str
    applicability: str
    stages: list[ProcessStage]


def process_to_dict(process):
    """Return nested dicts for the nested dataclasses of a process."""
    # asdict() will recursively convert any dataclass props to dicts.
    stages = [asdict(stage) for stage in process.stages]
    process_dict = {
        'name': process.name,
        'description': process.description,
        'applicability': process.applicability,
        'stages': stages,
    }
    return process_dict


# This page generates a preview of an email that can be sent
# to a mailing list to announce an intent.
# {feature_id}, {stage_id}, and {gate_id} are filled in by JS code.
INTENT_EMAIL_URL = '/feature/{feature_id}/gate/{gate_id}/intent'
INTENT_EMAIL_URL_NO_APPROVALS = '/feature/{feature_id}/stage/{stage_id}/intent'
LAUNCH_BUG_TEMPLATE_URL = '/admin/features/launch/{feature_id}?launch=1'
# TODO(jrobbins): Creation of the launch bug has been a TODO for 5 years.


PI_INITIAL_PUBLIC_PROPOSAL = ProgressItem(
    'Initial public proposal', 'initial_public_proposal_url'
)
PI_MOTIVATION = ProgressItem('Motivation', 'motivation')
PI_EXPLAINER = ProgressItem('Explainer', 'explainer_links')
PI_WEB_FEATURE = ProgressItem('Web feature', 'web_feature')
PI_TRACKING_BUG = ProgressItem('Tracking bug URL', 'bug_url')

PI_SPEC_LINK = ProgressItem('Spec link', 'spec_link')
PI_SPEC_MENTOR = ProgressItem('Spec mentor', 'spec_mentors')
PI_DRAFT_API_SPEC = ProgressItem('Draft API spec')
PI_I2P_EMAIL = ProgressItem(
    'Intent to Prototype email', 'intent_to_implement_url'
)

PI_SAMPLES = ProgressItem('Samples', 'sample_links')
PI_DRAFT_API_OVERVIEW = ProgressItem('Draft API overview (may be on MDN)')
PI_REQUEST_SIGNALS = ProgressItem('Request signals', 'safari_views')
PI_SEC_REVIEW = ProgressItem('Security review issues addressed')
PI_PRI_REVIEW = ProgressItem('Privacy review issues addressed')
# TODO(jrobbins): needs detector.
PI_EXTERNAL_REVIEWS = ProgressItem('External reviews')
PI_R4DT_EMAIL = ProgressItem(
    'Ready for Developer Testing email', 'announcement_url'
)

PI_TAG_REQUESTED = ProgressItem('TAG review requested', 'tag_review')
PI_VENDOR_SIGNALS = ProgressItem('Vendor signals', 'safari_views')
PI_WEB_DEV_SIGNALS = ProgressItem('Web developer signals', 'web_dev_views')
PI_DOC_LINKS = ProgressItem('Doc links', 'doc_links')
# TODO(jrobbins): needs detector.
PI_DOC_SIGNOFF = ProgressItem('Documentation signoff')
PI_EST_TARGET_MILESTONE = ProgressItem(
    'Estimated target milestone', 'shipped_milestone'
)

# TODO(jrobbins): needs detector.
PI_OT_REQUEST = ProgressItem('OT request')
# TODO(jrobbins): needs detector.
PI_OT_AVAILABLE = ProgressItem('OT available')
# TODO(jrobbins): needs detector.
PI_OT_RESULTS = ProgressItem('OT results')
PI_I2E_EMAIL = ProgressItem(
    'Intent to Experiment email', 'intent_to_experiment_url'
)
PI_I2E_LGTMS = ProgressItem('One LGTM on Intent to Experiment')

PI_MIGRATE_INCUBATION = ProgressItem('Request to migrate incubation')
PI_TAG_ADDRESSED = ProgressItem(
    'TAG review issues addressed', 'tag_review_status'
)
PI_UPDATED_VENDOR_SIGNALS = ProgressItem(
    'Updated vendor signals', 'safari_views'
)
PI_UPDATED_TARGET_MILESTONE = ProgressItem(
    'Updated target milestone', 'shipped_milestone'
)
PI_FINCH_FEATURE_OR_JUSTIFY = ProgressItem(
    'Finch feature name or non-finch justification', 'finch_name'
)
PI_I2S_EMAIL = ProgressItem('Intent to Ship email', 'intent_to_ship_url')
PI_I2S_LGTMS = ProgressItem('Three LGTMs on Intent to Ship')

# TODO(jrobbins): needs detector.
PI_FINAL_VENDOR_SIGNALS = ProgressItem('Final vendor signals', 'safari_views')
# TODO(jrobbins): needs detector.
PI_FINAL_TARGET_MILESTONE = ProgressItem(
    'Final target milestone', 'shipped_milestone'
)

PI_CODE_IN_CHROMIUM = ProgressItem('Code in Chromium')

PI_PSA_EMAIL = ProgressItem('Web facing PSA email')

# TODO(jrobbins): needs detector.
PI_DT_REQUEST = ProgressItem('DT request')
# TODO(jrobbins): needs detector.
PI_DT_AVAILABLE = ProgressItem('DT available')
# TODO(jrobbins): needs detector.
PI_REMOVAL_OF_DT = ProgressItem('Removal of DT')
PI_DT_EMAIL = ProgressItem(
    'Request for Deprecation Trial email', 'intent_to_experiment_url'
)
PI_DT_LGTMS = ProgressItem('One LGTM on Request for Deprecation Trial')

# TODO(jrobbins): needs detector.
PI_EXISTING_FEATURE = ProgressItem('Link to existing feature')

PI_CODE_REMOVED = ProgressItem('Code removed')

PI_ROLLOUT_IMPACT = ProgressItem('Rollout impact', 'rollout_impact')
PI_ROLLOUT_MILESTONE = ProgressItem('Rollout milestone', 'rollout_milestone')
PI_ROLLOUT_PLATFORMS = ProgressItem('Rollout platforms', 'rollout_platforms')
PI_ROLLOUT_DETAILS = ProgressItem('Rollout details', 'rollout_details')
PI_ROLLOUT_STAGE_PLAN = ProgressItem('Rollout stage plan', 'rollout_stage_plan')
PI_ENTERPRISE_POLICIES = ProgressItem(
    'Enterprise policies', 'enterprise_policies'
)  # noqa: E501


# This is a stage that can be inserted in the stages of any non-enterprise
# features that are marked as breaking changes.
FEATURE_ROLLOUT_STAGE = ProcessStage(
    'Rollout step',
    '',
    [
        PI_ROLLOUT_IMPACT,
        PI_ROLLOUT_MILESTONE,
        PI_ROLLOUT_PLATFORMS,
        PI_ROLLOUT_DETAILS,
        PI_ROLLOUT_STAGE_PLAN,
        PI_ENTERPRISE_POLICIES,
        PI_TRACKING_BUG,
    ],
    [],
    [],
    core_enums.INTENT_SHIP,
    core_enums.INTENT_ROLLOUT,
    stage_type=core_enums.STAGE_ENT_ROLLOUT,
)


BLINK_PROCESS_STAGES = [
    ProcessStage(
        'Start incubating',
        'Create an initial WebStatus feature entry and kick off standards '
        'incubation (WICG) to share ideas.',
        [
            PI_INITIAL_PUBLIC_PROPOSAL,
            PI_MOTIVATION,
            PI_EXPLAINER,
            PI_WEB_FEATURE,
            PI_TRACKING_BUG,
        ],
        [],
        [],
        core_enums.INTENT_NONE,
        core_enums.INTENT_INCUBATE,
        stage_type=core_enums.STAGE_BLINK_INCUBATE,
    ),
    ProcessStage(
        'Start prototyping',
        'Share an explainer doc and API. '
        'Start prototyping code in a public repo.',
        [
            PI_SPEC_LINK,
            PI_SPEC_MENTOR,
            PI_DRAFT_API_SPEC,
            PI_I2P_EMAIL,
        ],
        [
            Action(
                'Draft Intent to Prototype email',
                INTENT_EMAIL_URL,
                [
                    PI_TRACKING_BUG.name,
                    PI_INITIAL_PUBLIC_PROPOSAL.name,
                    PI_MOTIVATION.name,
                    PI_EXPLAINER.name,
                ],
            )
        ],
        [approval_defs.PrototypeApproval],
        core_enums.INTENT_INCUBATE,
        core_enums.INTENT_IMPLEMENT,
        stage_type=core_enums.STAGE_BLINK_PROTOTYPE,
    ),
    ProcessStage(
        'Dev trials and iterate on design',
        'Publicize availability for developers to try. '
        'Provide sample code. '
        'Request feedback from browser vendors.',
        [
            PI_SAMPLES,
            PI_DRAFT_API_OVERVIEW,
            PI_REQUEST_SIGNALS,
            PI_SEC_REVIEW,
            PI_PRI_REVIEW,
            PI_EXTERNAL_REVIEWS,
            PI_R4DT_EMAIL,
            PI_UPDATED_VENDOR_SIGNALS,
            PI_FINCH_FEATURE_OR_JUSTIFY,
        ],
        [
            Action(
                'Draft Ready for Developer Testing email',
                INTENT_EMAIL_URL_NO_APPROVALS,  # noqa: E501
                [
                    PI_TRACKING_BUG.name,
                    PI_INITIAL_PUBLIC_PROPOSAL.name,
                    PI_MOTIVATION.name,
                    PI_EXPLAINER.name,
                    PI_SPEC_LINK.name,
                ],
            )
        ],
        [],
        core_enums.INTENT_IMPLEMENT,
        core_enums.INTENT_EXPERIMENT,
        stage_type=core_enums.STAGE_BLINK_DEV_TRIAL,
    ),
    ProcessStage(
        'Evaluate readiness to ship',
        'Work through a TAG review and gather vendor signals.',
        [
            PI_TAG_REQUESTED,
            PI_VENDOR_SIGNALS,
            PI_WEB_DEV_SIGNALS,
            PI_DOC_LINKS,
            PI_DOC_SIGNOFF,
            PI_EST_TARGET_MILESTONE,
        ],
        [],
        [],
        core_enums.INTENT_EXPERIMENT,
        core_enums.INTENT_IMPLEMENT_SHIP,
        stage_type=core_enums.STAGE_BLINK_EVAL_READINESS,
    ),
    ProcessStage(
        'Origin Trial',
        '(Optional) Set up and run an origin trial. '
        'Act on feedback from partners and web developers.',
        [
            PI_OT_REQUEST,
            PI_OT_AVAILABLE,
            PI_OT_RESULTS,
            PI_I2E_EMAIL,
            PI_I2E_LGTMS,
        ],
        [
            Action(
                'Draft Intent to Experiment email',
                INTENT_EMAIL_URL,
                [
                    PI_TRACKING_BUG.name,
                    PI_INITIAL_PUBLIC_PROPOSAL.name,
                    PI_MOTIVATION.name,
                    PI_EXPLAINER.name,
                    PI_SPEC_LINK.name,
                    PI_EST_TARGET_MILESTONE.name,
                ],
            )
        ],
        [approval_defs.ExperimentApproval],
        core_enums.INTENT_IMPLEMENT_SHIP,
        core_enums.INTENT_ORIGIN_TRIAL,
        stage_type=core_enums.STAGE_BLINK_ORIGIN_TRIAL,
    ),
    ProcessStage(
        'Extend origin trial',
        '(Optional) Extend an existing origin trial.',
        [],
        [
            Action(
                'Draft Intent to Extend Experiment email', INTENT_EMAIL_URL, []
            )
        ],
        [approval_defs.ExtendExperimentApproval],
        core_enums.INTENT_ORIGIN_TRIAL,
        core_enums.INTENT_EXTEND_ORIGIN_TRIAL,
        stage_type=core_enums.STAGE_BLINK_EXTEND_ORIGIN_TRIAL,
    ),
    ProcessStage(
        'Prepare to ship',
        'Lock in shipping milestone. Finalize docs and announcements. '
        'Further standardization.',
        [
            PI_MIGRATE_INCUBATION,
            PI_TAG_ADDRESSED,
            PI_UPDATED_TARGET_MILESTONE,
            PI_I2S_EMAIL,
            PI_I2S_LGTMS,
        ],
        [
            Action(
                'Draft Intent to Ship email',
                INTENT_EMAIL_URL,
                [
                    PI_INITIAL_PUBLIC_PROPOSAL.name,
                    PI_MOTIVATION.name,
                    PI_EXPLAINER.name,
                    PI_SPEC_LINK.name,
                    PI_WEB_FEATURE.name,
                    PI_TRACKING_BUG.name,
                    PI_FINCH_FEATURE_OR_JUSTIFY.name,
                    PI_UPDATED_VENDOR_SIGNALS.name,
                    PI_TAG_ADDRESSED.name,
                    PI_UPDATED_TARGET_MILESTONE.name,
                ],
            )
        ],
        [approval_defs.ShipApproval],
        core_enums.INTENT_IMPLEMENT_SHIP,
        core_enums.INTENT_SHIP,
        stage_type=core_enums.STAGE_BLINK_SHIPPING,
    ),
    ProcessStage(
        'Ship',
        'Update milestones and other information when the feature '
        'actually ships.',
        [
            PI_FINAL_VENDOR_SIGNALS,
            PI_FINAL_TARGET_MILESTONE,
        ],
        [],
        [],
        core_enums.INTENT_SHIP,
        core_enums.INTENT_SHIPPED,
        stage_type=None,
    ),
    FEATURE_ROLLOUT_STAGE,
]


BLINK_LAUNCH_PROCESS = Process(
    'New feature incubation',
    'Description of blink launch process',  # Not used yet.
    'When to use it',  # Not used yet.
    BLINK_PROCESS_STAGES,
)


BLINK_FAST_TRACK_STAGES = [
    ProcessStage(
        'Start prototyping',
        'Write up use cases and scenarios, start coding as a '
        'runtime enabled feature.',
        [
            PI_SPEC_LINK,
            PI_CODE_IN_CHROMIUM,
            PI_WEB_FEATURE,
            PI_TRACKING_BUG,
        ],
        [
            Action(
                'Draft Intent to Prototype email',
                INTENT_EMAIL_URL,
                [PI_SPEC_LINK.name],
            )
        ],
        [approval_defs.PrototypeApproval],
        core_enums.INTENT_NONE,
        core_enums.INTENT_IMPLEMENT,
        stage_type=core_enums.STAGE_FAST_PROTOTYPE,
    ),
    ProcessStage(
        'Dev trials and iterate on implementation',
        'Publicize availability for developers to try. '
        'Provide sample code. '
        'Act on feedback from partners and web developers.',
        [
            PI_SAMPLES,
            PI_DRAFT_API_OVERVIEW,
            PI_R4DT_EMAIL,
            PI_VENDOR_SIGNALS,
            PI_EST_TARGET_MILESTONE,
            PI_FINCH_FEATURE_OR_JUSTIFY,
        ],
        [
            Action(
                'Draft Ready for Developer Testing email',
                INTENT_EMAIL_URL_NO_APPROVALS,  # noqa: E501
                [
                    PI_TRACKING_BUG.name,
                    PI_SPEC_LINK.name,
                    PI_EST_TARGET_MILESTONE.name,
                ],
            )
        ],
        [],
        core_enums.INTENT_IMPLEMENT,
        core_enums.INTENT_EXPERIMENT,
        stage_type=core_enums.STAGE_FAST_DEV_TRIAL,
    ),
    ProcessStage(
        'Origin Trial',
        '(Optional) Set up and run an origin trial. '
        'Act on feedback from partners and web developers.',
        [
            PI_OT_REQUEST,
            PI_OT_AVAILABLE,
            PI_OT_RESULTS,
            PI_I2E_EMAIL,
            PI_I2E_LGTMS,
        ],
        [
            Action(
                'Draft Intent to Experiment email',
                INTENT_EMAIL_URL,
                [
                    PI_TRACKING_BUG.name,
                    PI_SPEC_LINK.name,
                    PI_EST_TARGET_MILESTONE.name,
                ],
            )
        ],
        [approval_defs.ExperimentApproval],
        core_enums.INTENT_EXPERIMENT,
        core_enums.INTENT_ORIGIN_TRIAL,
        stage_type=core_enums.STAGE_FAST_ORIGIN_TRIAL,
    ),
    ProcessStage(
        'Extend origin trial',
        '(Optional) Extend an existing origin trial.',
        [],
        [
            Action(
                'Draft Intent to Extend Experiment email', INTENT_EMAIL_URL, []
            )
        ],
        [approval_defs.ExtendExperimentApproval],
        core_enums.INTENT_ORIGIN_TRIAL,
        core_enums.INTENT_EXTEND_ORIGIN_TRIAL,
        stage_type=core_enums.STAGE_FAST_EXTEND_ORIGIN_TRIAL,
    ),
    ProcessStage(
        'Prepare to ship',
        'Lock in shipping milestone. Finalize docs and announcements. '
        'Further standardization.',
        [
            PI_DOC_SIGNOFF,
            PI_UPDATED_TARGET_MILESTONE,
            PI_I2S_EMAIL,
            PI_I2S_LGTMS,
        ],
        [
            Action(
                'Draft Intent to Ship email',
                INTENT_EMAIL_URL,
                [
                    PI_TRACKING_BUG.name,
                    PI_SPEC_LINK.name,
                    PI_WEB_FEATURE.name,
                    PI_FINCH_FEATURE_OR_JUSTIFY.name,
                    PI_UPDATED_TARGET_MILESTONE.name,
                ],
            )
        ],
        [approval_defs.ShipApproval],
        core_enums.INTENT_EXPERIMENT,
        core_enums.INTENT_SHIP,
        stage_type=core_enums.STAGE_FAST_SHIPPING,
    ),
    ProcessStage(
        'Ship',
        'Update milestones and other information when the feature '
        'actually ships.',
        [
            PI_FINAL_VENDOR_SIGNALS,
            PI_FINAL_TARGET_MILESTONE,
        ],
        [],
        [],
        core_enums.INTENT_SHIP,
        core_enums.INTENT_SHIPPED,
        stage_type=None,
    ),
    FEATURE_ROLLOUT_STAGE,
]


BLINK_FAST_TRACK_PROCESS = Process(
    'Existing feature implementation',
    'Description of blink fast track process',  # Not used yet.
    'When to use it',  # Not used yet.
    BLINK_FAST_TRACK_STAGES,
)


PSA_ONLY_STAGES = [
    ProcessStage(
        'Implement',
        'Check code into Chromium under a flag.',
        [PI_SPEC_LINK, PI_CODE_IN_CHROMIUM, PI_WEB_FEATURE, PI_TRACKING_BUG],
        [],
        [],
        core_enums.INTENT_NONE,
        core_enums.INTENT_IMPLEMENT,
        stage_type=core_enums.STAGE_PSA_IMPLEMENT,
    ),
    ProcessStage(
        'Dev trials and iterate on implementation',
        '(Optional) Publicize availability for developers to try. '
        'Act on feedback from partners and web developers.',
        [
            PI_R4DT_EMAIL,
            PI_VENDOR_SIGNALS,
            PI_EST_TARGET_MILESTONE,
            PI_FINCH_FEATURE_OR_JUSTIFY,
        ],
        [
            Action(
                'Draft Ready for Developer Testing email',
                INTENT_EMAIL_URL_NO_APPROVALS,  # noqa: E501
                [
                    PI_TRACKING_BUG.name,
                    PI_SPEC_LINK.name,
                    PI_EST_TARGET_MILESTONE.name,
                ],
            )
        ],
        [],
        core_enums.INTENT_IMPLEMENT,
        core_enums.INTENT_EXPERIMENT,
        stage_type=core_enums.STAGE_PSA_DEV_TRIAL,
    ),
    ProcessStage(
        'Prepare to ship',
        'Lock in shipping milestone.',
        [
            PI_PSA_EMAIL,
            PI_UPDATED_TARGET_MILESTONE,
            PI_I2S_EMAIL,
        ],
        [
            Action(
                'Draft Web-Facing Change PSA email',
                INTENT_EMAIL_URL_NO_APPROVALS,  # noqa: E501
                [
                    PI_TRACKING_BUG.name,
                    PI_SPEC_LINK.name,
                    PI_FINCH_FEATURE_OR_JUSTIFY.name,
                    PI_UPDATED_TARGET_MILESTONE.name,
                ],
            )
        ],
        [approval_defs.ShipApproval],
        core_enums.INTENT_EXPERIMENT,
        core_enums.INTENT_SHIP,
        stage_type=core_enums.STAGE_PSA_SHIPPING,
    ),
    ProcessStage(
        'Ship',
        'Update milestones and other information when the feature '
        'actually ships.',
        [
            PI_FINAL_VENDOR_SIGNALS,
            PI_FINAL_TARGET_MILESTONE,
        ],
        [],
        [],
        core_enums.INTENT_SHIP,
        core_enums.INTENT_SHIPPED,
        stage_type=None,
    ),
    FEATURE_ROLLOUT_STAGE,
]


PSA_ONLY_PROCESS = Process(
    'Web developer facing change to existing code',
    'Description of PSA process',  # Not used yet.
    'When to use it',  # Not used yet.
    PSA_ONLY_STAGES,
)


DEPRECATION_STAGES = [
    ProcessStage(
        'Write up deprecation plan',
        'Create an initial WebStatus feature entry to deprecate '
        'an existing feature, including motivation and impact. '
        'Then, get approval for your deprecation plans.',
        [
            PI_EXISTING_FEATURE,
            PI_MOTIVATION,
            PI_TRACKING_BUG,
        ],
        [
            Action(
                'Draft Intent to Deprecate and Remove email',
                INTENT_EMAIL_URL,
                [
                    PI_TRACKING_BUG.name,
                    PI_MOTIVATION.name,
                    PI_TRACKING_BUG.name,
                ],
            )
        ],
        [approval_defs.PrototypeApproval],
        core_enums.INTENT_NONE,
        core_enums.INTENT_IMPLEMENT,
        stage_type=core_enums.STAGE_DEP_PLAN,
    ),
    # TODO(cwilso): Work out additional steps for flag defaulting to disabled.
    ProcessStage(
        'Dev trial of deprecation',
        'Publicize deprecation and address risks. ',
        [
            PI_R4DT_EMAIL,
            PI_VENDOR_SIGNALS,
            PI_EST_TARGET_MILESTONE,
            PI_FINCH_FEATURE_OR_JUSTIFY,
        ],
        [
            Action(
                'Draft Ready for Developer Testing email',
                INTENT_EMAIL_URL_NO_APPROVALS,  # noqa: E501
                [
                    PI_TRACKING_BUG.name,
                    PI_MOTIVATION.name,
                    PI_VENDOR_SIGNALS.name,
                    PI_EST_TARGET_MILESTONE.name,
                ],
            )
        ],
        [],
        core_enums.INTENT_IMPLEMENT,
        core_enums.INTENT_EXPERIMENT,
        stage_type=core_enums.STAGE_DEP_DEV_TRIAL,
    ),
    ProcessStage(
        'Prepare for Deprecation Trial',
        '(Optional) Set up and run a deprecation trial. ',
        [
            PI_DT_REQUEST,
            PI_DT_AVAILABLE,
            PI_REMOVAL_OF_DT,
            PI_DT_EMAIL,
            PI_DT_LGTMS,
        ],
        [
            Action(
                'Draft Request for Deprecation Trial email',
                INTENT_EMAIL_URL,
                [
                    PI_TRACKING_BUG.name,
                    PI_MOTIVATION.name,
                    PI_VENDOR_SIGNALS.name,
                    PI_EST_TARGET_MILESTONE.name,
                ],
            )
        ],
        [approval_defs.ExperimentApproval],
        core_enums.INTENT_EXPERIMENT,
        core_enums.INTENT_ORIGIN_TRIAL,
        stage_type=core_enums.STAGE_DEP_DEPRECATION_TRIAL,
    ),
    ProcessStage(
        'Extend deprecation trial',
        '(Optional) Extend an existing deprecation trial.',
        [],
        [
            Action(
                'Draft Intent to Extend Deprecation Trial email',
                INTENT_EMAIL_URL,
                [],
            )
        ],
        [approval_defs.ExtendExperimentApproval],
        core_enums.INTENT_ORIGIN_TRIAL,
        core_enums.INTENT_EXTEND_ORIGIN_TRIAL,
        stage_type=core_enums.STAGE_DEP_EXTEND_DEPRECATION_TRIAL,
    ),
    ProcessStage(
        'Prepare to ship',
        'Lock in shipping milestone. '
        'Finalize docs and announcements before disabling feature by default. '
        'If there were changes since your plan approvals, get approvals again.',
        [
            PI_UPDATED_TARGET_MILESTONE,
            PI_I2S_EMAIL,
            PI_I2S_LGTMS,
        ],
        [
            Action(
                'Draft Intent to Ship email',
                INTENT_EMAIL_URL,
                [
                    PI_TRACKING_BUG.name,
                    PI_MOTIVATION.name,
                    PI_FINCH_FEATURE_OR_JUSTIFY.name,
                    PI_VENDOR_SIGNALS.name,
                    PI_UPDATED_TARGET_MILESTONE.name,
                ],
            )
        ],
        [approval_defs.ShipApproval],
        core_enums.INTENT_EXPERIMENT,
        core_enums.INTENT_SHIP,
        stage_type=core_enums.STAGE_DEP_SHIPPING,
    ),
    ProcessStage(
        'Remove code',
        'Once the feature is no longer available, remove the code.',
        [
            PI_CODE_REMOVED,
        ],
        [
            Action(
                'Generate an Intent to Extend Deprecation Trial',
                INTENT_EMAIL_URL,
                [
                    PI_TRACKING_BUG.name,
                    PI_MOTIVATION.name,
                    PI_VENDOR_SIGNALS.name,
                    PI_UPDATED_TARGET_MILESTONE.name,
                ],
            ),
        ],
        [],
        core_enums.INTENT_SHIP,
        core_enums.INTENT_REMOVED,
        stage_type=core_enums.STAGE_DEP_REMOVE_CODE,
    ),
    FEATURE_ROLLOUT_STAGE,
]

# Thise are the stages for a feature that has the enterprise feature type.
ENTERPRISE_STAGES = [
    ProcessStage(
        'Rollout step',
        '',
        [
            PI_ROLLOUT_IMPACT,
            PI_ROLLOUT_MILESTONE,
            PI_ROLLOUT_PLATFORMS,
            PI_ROLLOUT_DETAILS,
            PI_ROLLOUT_STAGE_PLAN,
            PI_ENTERPRISE_POLICIES,
        ],
        [],
        [],
        core_enums.INTENT_NONE,
        core_enums.INTENT_ROLLOUT,
        stage_type=core_enums.STAGE_ENT_ROLLOUT,
    ),
]


DEPRECATION_PROCESS = Process(
    'Feature deprecation',
    'Description of deprecation process',  # Not used yet.
    'When to use it',  # Not used yet.
    DEPRECATION_STAGES,
)


ENTERPRISE_PROCESS = Process(
    'New Feature or removal affecting enterprises',
    'Description of enterprise process',  # Not used yet.
    'When to use it',  # Not used yet.
    ENTERPRISE_STAGES,
)


ALL_PROCESSES = {
    core_enums.FEATURE_TYPE_INCUBATE_ID: BLINK_LAUNCH_PROCESS,
    core_enums.FEATURE_TYPE_EXISTING_ID: BLINK_FAST_TRACK_PROCESS,
    core_enums.FEATURE_TYPE_CODE_CHANGE_ID: PSA_ONLY_PROCESS,
    core_enums.FEATURE_TYPE_DEPRECATION_ID: DEPRECATION_PROCESS,
    core_enums.FEATURE_TYPE_ENTERPRISE_ID: ENTERPRISE_PROCESS,
}


def initial_tag_review_status(feature_type):
    """Incubating a new feature requires a TAG review, other types do not."""
    if feature_type == core_enums.FEATURE_TYPE_INCUBATE_ID:
        return core_enums.REVIEW_PENDING
    return core_enums.REVIEW_NA


def review_is_done(status):
    """Determine if a review status indicates the review is complete.

    Args:
        status: The review status code to check.

    Returns:
        True if the review is done or not applicable, False otherwise.
    """
    return status in (core_enums.REVIEW_ISSUES_ADDRESSED, core_enums.REVIEW_NA)


# These functions return a true value when the checkmark should be shown.
# If they return a string, and it starts with "http:" or "https:", it will
# be used as a link URL.
PROGRESS_DETECTORS = {
    'Initial public proposal': lambda f, _: f.initial_public_proposal_url,
    'Explainer': lambda f, _: f.explainer_links and f.explainer_links[0],
    'Web feature': lambda f, _: (
        f.web_feature
        and f.web_feature != WebDXFeatureObserver.MISSING_FEATURE_ID
    ),  # noqa: E501
    'Tracking bug URL': lambda f, _: f.bug_url,
    'Security review issues addressed': lambda f, _: review_is_done(
        f.security_review_status
    ),
    'Privacy review issues addressed': lambda f, _: review_is_done(
        f.privacy_review_status
    ),
    'Intent to Prototype email': lambda f, stages: (
        core_enums.STAGE_TYPES_PROTOTYPE[f.feature_type]
        and stages[core_enums.STAGE_TYPES_PROTOTYPE[f.feature_type]][
            0
        ].intent_thread_url
    ),
    'Intent to Ship email': lambda f, stages: (
        core_enums.STAGE_TYPES_SHIPPING[f.feature_type]
        and stages[core_enums.STAGE_TYPES_SHIPPING[f.feature_type]][
            0
        ].intent_thread_url
    ),
    'Ready for Developer Testing email': lambda f, stages: (
        core_enums.STAGE_TYPES_DEV_TRIAL[f.feature_type]
        and stages[core_enums.STAGE_TYPES_DEV_TRIAL[f.feature_type]][
            0
        ].announcement_url
    ),
    'Intent to Experiment email': lambda f, stages: (
        core_enums.STAGE_TYPES_ORIGIN_TRIAL[f.feature_type]
        and stages[core_enums.STAGE_TYPES_ORIGIN_TRIAL[f.feature_type]][
            0
        ].intent_thread_url
    ),
    'Samples': lambda f, _: f.sample_links and f.sample_links[0],
    'Doc links': lambda f, _: f.doc_links and f.doc_links[0],
    'Spec link': lambda f, _: f.spec_link,
    'Draft API spec': lambda f, _: f.spec_link,
    'API spec': lambda f, _: f.api_spec,
    'Spec mentor': lambda f, _: f.spec_mentor_emails,
    'TAG review requested': lambda f, _: f.tag_review,
    'TAG review issues addressed': lambda f, _: review_is_done(
        f.tag_review_status
    ),
    'Web developer signals': lambda f, _: bool(
        f.web_dev_views and f.web_dev_views != core_enums.DEV_NO_SIGNALS
    ),
    'Vendor signals': lambda f, _: bool(
        f.ff_views != core_enums.NO_PUBLIC_SIGNALS
        or f.safari_views != core_enums.NO_PUBLIC_SIGNALS
    ),
    'Updated vendor signals': lambda f, _: bool(
        f.ff_views != core_enums.NO_PUBLIC_SIGNALS
        or f.safari_views != core_enums.NO_PUBLIC_SIGNALS
    ),
    'Final vendor signals': lambda f, _: bool(
        f.ff_views != core_enums.NO_PUBLIC_SIGNALS
        or f.safari_views != core_enums.NO_PUBLIC_SIGNALS
    ),
    'Estimated target milestone': lambda f, stages: bool(
        core_enums.STAGE_TYPES_SHIPPING[f.feature_type]
        and stages[core_enums.STAGE_TYPES_SHIPPING[f.feature_type]][
            0
        ].milestones  # noqa: E501
        and stages[core_enums.STAGE_TYPES_SHIPPING[f.feature_type]][
            0
        ].milestones.desktop_first
    ),
    'Updated target milestone': lambda f, stages: bool(
        core_enums.STAGE_TYPES_SHIPPING[f.feature_type]
        and stages[core_enums.STAGE_TYPES_SHIPPING[f.feature_type]][
            0
        ].milestones  # noqa: E501
        and stages[core_enums.STAGE_TYPES_SHIPPING[f.feature_type]][
            0
        ].milestones.desktop_first
    ),
    'Final target milestone': lambda f, stages: bool(
        core_enums.STAGE_TYPES_SHIPPING[f.feature_type]
        and stages[core_enums.STAGE_TYPES_SHIPPING[f.feature_type]][
            0
        ].milestones  # noqa: E501
        and stages[core_enums.STAGE_TYPES_SHIPPING[f.feature_type]][
            0
        ].milestones.desktop_first
    ),
    'Finch feature name or non-finch justification': lambda f, stages: bool(
        f.finch_name or f.non_finch_justification
    ),
    'Code in Chromium': lambda f, _: (
        f.impl_status_chrome
        in (
            core_enums.IN_DEVELOPMENT,
            core_enums.BEHIND_A_FLAG,
            core_enums.ENABLED_BY_DEFAULT,
            core_enums.ORIGIN_TRIAL,
        )
    ),
    'Motivation': lambda f, _: bool(f.motivation),
    'Code removed': lambda f, _: f.impl_status_chrome == core_enums.REMOVED,
    'Rollout impact': lambda f, stages: (
        stages[core_enums.STAGE_TYPES_ROLLOUT[f.feature_type]]
        and stages[core_enums.STAGE_TYPES_ROLLOUT[f.feature_type]][
            0
        ].rollout_impact
    ),
    'Rollout milestone': lambda f, stages: (
        stages[core_enums.STAGE_TYPES_ROLLOUT[f.feature_type]]
        and stages[core_enums.STAGE_TYPES_ROLLOUT[f.feature_type]][
            0
        ].rollout_milestone
    ),
    'Rollout platforms': lambda f, stages: (
        stages[core_enums.STAGE_TYPES_ROLLOUT[f.feature_type]]
        and stages[core_enums.STAGE_TYPES_ROLLOUT[f.feature_type]][
            0
        ].rollout_platforms
    ),
    'Rollout details': lambda f, stages: (
        stages[core_enums.STAGE_TYPES_ROLLOUT[f.feature_type]]
        and stages[core_enums.STAGE_TYPES_ROLLOUT[f.feature_type]][
            0
        ].rollout_details
    ),
    'Rollout stage plan': lambda f, stages: (
        stages[core_enums.STAGE_TYPES_ROLLOUT[f.feature_type]]
        and stages[core_enums.STAGE_TYPES_ROLLOUT[f.feature_type]][
            0
        ].rollout_stage_plan
    ),
    'Enterprise policies': lambda f, stages: (
        stages[core_enums.STAGE_TYPES_ROLLOUT[f.feature_type]]
        and stages[core_enums.STAGE_TYPES_ROLLOUT[f.feature_type]][
            0
        ].enterprise_policies
    ),
}


def write_gates_and_stages_for_feature(
    feature_id: int, feature_type: int
) -> None:
    """Write each Stage and Gate entity for the given feature."""
    # Obtain a list of stages and gates for the given feature type.
    stages_gates = core_enums.STAGES_AND_GATES_BY_FEATURE_TYPE[feature_type]

    for stage_type, gate_types in stages_gates:
        # Don't create a trial extension stage pre-emptively.
        if (
            stage_type
            == core_enums.STAGE_TYPES_EXTEND_ORIGIN_TRIAL[feature_type]
        ):
            continue

        stage = Stage(feature_id=feature_id, stage_type=stage_type)
        stage.put()
        # Stages can have zero or more gates.
        for gate_type in gate_types:
            gate = Gate(
                feature_id=feature_id,
                stage_id=stage.key.integer_id(),
                gate_type=gate_type,
                state=Gate.PREPARING,
            )
            gate.put()
